Updating and deleting the MySQLite database through dynamic button on QTableWidget - python-3.x

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.

Related

QComboBox only gives first item

I have some troubles with a QComboBox() when I want to read out the actual text from it.
The ComboBox is created dynamically, so I use the exec() function from python to create them.
I see the ComboBoxes in my layout - but so far so good - when I read the Data out from them, I only get returned the first value of the ComboBox, not the actual selected. I have tried it with .currentText() and .currentIndex()
As you see in my code snipped in the upper part - I create zu QComboBox with the exec function (the program is used to import data on a Database, so, I create QComboBoxes, as much as Database-Tableccolumns I have and so much entries, Import-Tablecolumns I have.
I create the ComboBox with startin CB_,and the I look for attributes starting with CB and run their function.
But I have no clue, what is wrong..
Here is my code:
self.layoutscroll = QVBoxLayout()
self.widgetscroll = QWidget()
self.widgetscroll.setLayout(self.layoutscroll)
self.nextbutton.setEnabled(True)
self.nextbutton.clicked.connect(self.uploaddatatodb)
self.scroll.setWidget(self.widgetscroll)
self.layoutbox.addWidget(self.scroll)
for col in dfdb.columns:
exec("self.groupbox_{0} = QGroupBox('Datenbank: {0}')".format(str(col)))
exec("self.groupbox_{0}_layout = QVBoxLayout()".format(str(col)))
exec("self.CB_{0} = QComboBox(self)".format(str(col)))
exec("for importcol in self.dfimport.columns:\n\tself.CB_{0}.addItem(str(importcol))".format(str(col)))
exec("self.groupbox_{0}_layout.addWidget(self.CB_{0})".format(str(col)))
exec("self.groupbox_{0}.setLayout(self.groupbox_{0}_layout)".format(str(col)))
exec("self.layoutscroll.addWidget(self.groupbox_{0})".format(str(col)))
def uploaddatatodb(self):
print("hallo")
self.nextbutton.setEnabled(False)
comparedf = pd.DataFrame()
dblist = [a for a in dir(self) if a.startswith('groupbox_') and not a.endswith('layout')]
importlist = [a for a in dir(self) if a.startswith('CB_')]
for idx,x in enumerate(importlist):
exec("str(x)")
exec("val=str(self."+str(x)+".currentText())")
exec("print(val)")
exec("importlist["+str(idx)+"]=val")
for i in range(len(dblist)):
comparedf = comparedf.append({"db":dblist[i][9:],"import":importlist[i]},ignore_index=True)
print(comparedf)
EDIT:
I have updated my code to get away from the exec() function - but I have the same error: When I want to read out the comboboxes - everytime I get only the first value of the combobox:
self.layoutscroll = QVBoxLayout()
self.widgetscroll = QWidget()
self.widgetscroll.setLayout(self.layoutscroll)
self.nextbutton.setEnabled(True)
self.nextbutton.clicked.connect(self.uploaddatatodb)
self.scroll.setWidget(self.widgetscroll)
self.layoutbox.addWidget(self.scroll)
for col in dfdb.columns:
combo = QComboBox()
combo.addItems(self.dfimport.columns)
groupbox_DB_lyt = QVBoxLayout()
groupbox_DB = QGroupBox(str("Datenbank: "+col))
groupbox_DB_lyt.addWidget(combo)
groupbox_DB.setLayout(groupbox_DB_lyt)
self.layoutscroll.addWidget(groupbox_DB)
def uploaddatatodb(self):
self.nextbutton.setEnabled(False)
for i in range(self.layoutscroll.count()):
dblist = self.layoutscroll.itemAt(i).widget().title()
importlist = self.layoutscroll.itemAt(i).widget().layout().itemAt(0).widget().currentText()
print(dblist,importlist)
With print I get over the iteration all Groupbox Titles, but only the first QComboBoxes first Text.

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.

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 integrate 2 different python 3 tkinter GUI programs with different geometry managers

Iam new to python. and I'm trying to create a small GUI application using Tkinter in python3. The functionality that I want to achieve is that
1) the program has to create a small window which takes in a search string from the user.
enter image description here
2) once the user enters the string and hits the search button, the program has to retrieve data from an excel sheet and show up the results in a Tkinter table (pandas table module).
I have written code separately for both of these functionalities and unable to put them together
here is the code for achieving the functionality 1.
from tkinter import *
from pandastable import Table, TableModel
import pandas as pd
# Instance of class tkinter Tk
root = Tk()
# Icon for the window
root.iconbitmap('D:/icon.ico')
# Window Title
root.title('Tri2Find')
df_in = pd.read_excel('D:/tmp/data.xlsx',index_col = None)
# Input variable for entry string
entry_search_string = StringVar()
# Text Field for search string
entry_search = Entry(root, width = 50,textvariable =
entry_search_string).grid(row = 0, column = 1)
# Function for different actions of [GO SEARCH!!] Button
def button_entry_search():
search_string = entry_search_string.get()
# Create a label when button is clicked and print the search term in the label
label_entry_search = Label(root, text = "SEARCHING : "+search_string ).grid(row = 0, column = 2)
# Creating a list for holding the index values
index = []
# Iterating over each row of the data frame
for i in range(len(df_in.index)):
# Converting each row of a data frame into a pandas series
row = pd.Series(df_in.iloc[i,:])
# Check for the user's search token in each row
pattern_boolean = row.str.contains(search_string, case = False, na = False)
# If presence of token is true
if pattern_boolean.any() == True:
# Then append the value of i to the index
index.append(i) # Index contains the row indicies with the required search term
# Data frame which contains the rows with required search term
df_out = df_in.iloc[index,:]
print(df_out)
# [GO SEARCH!!] Button of search term
button_search = Button(root,text = "GO SEARCH!!", width = 13,command =
button_entry_search).grid(row = 0)
# loop function to Run and keep the GUI open
root.mainloop()
The above code takes in the string and prints the results to the console but not to the tkinter table
Here is the code for functionality 2.
from tkinter import *
from pandastable import Table, TableModel
import pandas as pd
# Reading the excel from local path
df_in = pd.read_excel('D:/tmp/data.xlsx',index_col = None)
# Reading user input from console
search_token = input("Please Enter the search term :")
# Print a status message
print("Showing results for..." +str(search_token))
# Creating a list for holding the index values
index = []
# Iterating over each row of the data frame
for i in range(len(df_in.index)):
# Converting each row of a data frame into a pandas series
row = pd.Series(df_in.iloc[i,:])
# Check for the user's search token in each row
pattern_boolean = row.str.contains(search_token, case = False, na = False)
# If presence of token is true
if pattern_boolean.any() == True:
# Then append the value of i to the index
index.append(i) # Index contains the row indicies with the required search term
# Data frame which contains the rows with required search term
df_out = df_in.iloc[index,:]
class results_table(Frame):
def __init__(self, parent=None):
self.parent = parent
Frame.__init__(self)
self.main = self.master
self.main.geometry('600x400+200+100')
self.main.iconbitmap('D:/icon.ico')
self.main.title('Tri2Find')
f = Frame(self.main)
f.pack(fill=BOTH,expand=1)
self.table=Table(f, dataframe=df_out,showtoolbar=True, showstatusbar=True)
self.table.show()
return
app = results_table()
#launch the app
app.mainloop()
The above code takes input from the console but gives the output to the Tkinter table.
I need help to integrate these 2 pieces of code into a single file.
The user enters the search string and hits search
Then the data retrieved should show up in the table.
I assume the error is with the geometry manager because iam trying to use pack() and grid() types in a single instance. But I really dont know how to put these 2 pieces of code without any conflict and acheive the functionality. Iam new to OOP concept of python too.
Thanks in advance.
Regarding point 1): TL;DR, why so much code. I didn't read through it but it seems a whole lot to read a string off the user. Does it do something else as well?
Why not just use a simpledialog?
message = simpledialog.askstring('Tri2Find','Enter text',parent=root)
Or if you need to have your own design and/or validation you can build your own dialog inheriting from simpledialog.

Resources