QTableView: Prevent a user from navigating away from a spesific row - pyqt

I am having trouble preventing the user form changing the current selection, if the save action did not complete successfully. I can re-select a row using the QTableView's selection models' currentRowChanged signal but although the selection change, the blue selection indicator does not. See the image below.
Example: In the image below the user attempted to add a new row nr 537. But the save action got an error and I don't want the user to navigate away from row 537 before the record is either deleted or edited and then saved
Question: How do I move the blue line to the current selection? (the current selection is the last row) (The QTableView's Selection Behavior is set to select rows)
Here is the code I got so far:
def __init__(self, parent):
...
self.__tableViewSelectionModel = self.__ui.tableView.selectionModel()
self.__tableViewSelectionModel.currentRowChanged.connect(self.rowChanged)
def rowChanged(self, current=None, previous=None):
if save() == True:
self.__ui.tableView.clearSelection()
self.__ui.tableView.selectRow(previous.row())

Replacing this:
self.__ui.tableView.clearSelection()
self.__ui.tableView.selectRow(previous.row())
with this:
QtCore.QTimer.singleShot(0.00001, lambda: self.__ui.tableView.selectRow(previous.row()))
produced the desired outcome

Related

Tkinter Listbox: programmatically selecting Listbox item disables up/down cursor keys

The following code creates a standard listbox in tkinter. In normal use, after the user makes the first listbox selection with a mouse click, subsequent selections can be made either by clicking again with the mouse, or by using the up/down cursor keys. Both of these (mouse click and cursor keys) correctly trigger ListboxSelect and change the value of SelectedIndex using the associated function UserClickedSelection(event).
The code also includes a function that allows me to set a listbox selection programmatically. This works perfectly, and correctly changes SelectedIndex using the SetSelection(index) function. But...and this is the problem...after making a selection programmatically, the up/down arrow keys no longer function. The user needs to make the next selection using the mouse, and then the up/down arrow keys once again function normally.
import tkinter as tk
root = tk.Tk()
root.geometry("300x300")
SelectedIndex = 0
def UserClickedSelection(event): # get user selection
global SelectedIndex
SelectedIndex = mylistbox.curselection()[0]
def SetSelection(index): # set selection
global SelectedIndex
mylistbox.selection_clear(0,tk.END)
mylistbox.selection_set(index)
mylistbox.activate(index)
SelectedIndex = mylistbox.curselection()[0]
mylistbox = tk.Listbox(root,activestyle="none")
mylistbox.place(x=0,y=0)
mylistbox.config(selectmode=tk.BROWSE)
mylistbox.config(exportselection=False)
mylistbox.bind('<<ListboxSelect>>',UserClickedSelection)
mylist = ['Zero','One','Two','Three','Four','Five']
mylistbox.insert(0,*mylist)
# Using function: set selection to index 2
SetSelection(2)
root.mainloop()
As you can see, in the code I've used the programmatic function to select index 2 of the listbox. This works fine, but the up/down arrow keys do not work until another selection is made afterwards using a mouse click. So there is clearly a difference between a "real" user selection and my "programmatic" selection. Is there some way to rectify this? I need the up/down arrow keys to work consistently. Thanks in advance for any assistance.
If I understand the problem correctly, you just need to make sure the listbox has focus.
def SetSelection(index): # set selection
...
mylistbox.focus_set()

Qt: QFormLayout - How to find the row from the button

How to find the row number of QFormLayout() from a button which is in that row? I have a delete button on each row of the form. Such that if I click the delete button, that specific row will get deleted. For this, I am planning to use the command QtWidgets.QFormLayout.removeRow(row) command. I have defined the QFormLayout() within my def __init__(self): function like so.
self.attachForm = QFormLayout()
I also have an Add button which calls the self.attachBtn_clicked(self) function given below. So every time the Add button is clicked a new row is added. Any help will be appreciated.
def attachBtn_clicked(self):
hbox = QHBoxLayout()
self.attachForm.addRow('',hbox)
browseBtn = QPushButton("Open")
hbox.addWidget(browseBtn)
addAttachEdit = QLineEdit()
hbox.addWidget(addAttachEdit)
delAttachBtn = QPushButton("x")
delAttachBtn.setFixedSize(15,15)
delAttachBtn.clicked.connect(self.delAttachBtn_clicked)
hbox.addWidget(delAttachBtn)
The objective is now to write the self.delAttachBtn_clicked(self) function which will delete the specific row.
You can iterate through the rows and find the button that matches the sender.
def delAttachBtn_clicked(self):
for i in range(self.attachForm.rowCount()):
if self.sender() == self.attachForm.itemAt(i, QFormLayout.FieldRole).itemAt(2).widget():
self.attachForm.removeRow(i)
return
itemAt(2) is used since delAttachBtn is the 3rd item in each QHBoxLayout.

I need help in Python with displaying the contents of a 2D Set into a Tkinter Textbox

Disclaimer: I have only begun to learn about Python. I took a crash course just to learn the very basics about a month ago and the rest of my efforts to learn have all been research thru Google and looking at solutions here in Stack Overflow.
I am trying to create an application that will read all PDF files stored in a folder and extract their filenames, page numbers, and the contents of the first page, and store this information into a 2D set. Once this is done, the application will create a tkinter GUI with 2 listboxes and 1 text box.
The application should display the PDF filenames in the first listbox, and the corresponding page numbers of each file in the second listbox. Both listboxes are synched in scrolling.
The text box should display the text contents on the first page of the PDF.
What I want to happen is that each time I click a PDF filename in the first listbox with the mouse or with up or down arrow keys, the application should display the contents of the first page of the selected file in the text box.
This is how my GUI looks and how it should function
https://i.stack.imgur.com/xrkvo.jpg
I have been successful in all other requirements so far except the part where when I select a filename in the first listbox, the contents of the first page of the PDF should be displayed in the text box.
Here is my code for populating the listboxes and text box. The contents of my 2D set pdfFiles is [['PDF1 filename', 'PDF1 total pages', 'PDF1 text content of first page'], ['PDF2 filename', 'PDF2 total pages', 'PDF2 text content of first page'], ... etc.
===========Setting the Listboxes and Textbox=========
scrollbar = Scrollbar(list_2)
scrollbar.pack(side=RIGHT, fill=Y)
list_1.config(yscrollcommand=scrollbar.set)
list_1.bind("<MouseWheel>", scrolllistbox2)
list_2.config(yscrollcommand=scrollbar.set)
list_2.bind("<MouseWheel>", scrolllistbox1)
txt_3 = tk.Text(my_window, font='Arial 10', wrap=WORD)
txt_3.place(relx=0.5, rely=0.12, relwidth=0.472, relheight=0.86)
scrollbar = Scrollbar(txt_3)
scrollbar.pack(side=RIGHT, fill=Y)
list_1.bind("<<ListboxSelect>>", CurSelect)
============Populating the Listboxes with the content of the 2D Set===
i = 0
while i < count:
list_1.insert(tk.END, pdfFiles[i][0])
list_2.insert(tk.END, pdfFiles[i][1])
i = i + 1
============Here is my code for CurSelect function========
def CurSelect(evt):
values = [list_1.get(idx) for idx in list_1.curselection()]
print(", ".join(values)) ????
========================
The print command above is just my test command to show that I have successfully extracted the selected item in the listbox. What I need now is to somehow link that information to its corresponding page content in my 2D list and display it in the text box.
Something like:
1) select the filename in the listbox
2) link the selected filename to the filenames stored in the pdfFilename 2D set
3) once filename is found, identify the corresponding text of the first page
4) display the text of the first page of the selected file in the text box
I hope I am making sense. Please help.
You don't need much to finish it. You just need some small things:
1. Get the selected item of your listbox:
selected_indexes = list_1.curselection()
first_selected = selected_indexes[0] # it's possible to select multiple items
2. Get the corresponding PDF text:
pdf_text = pdfFiles[first_selected][2]
3. Change the text of your Text widget: (from https://stackoverflow.com/a/20908371/8733066)
txt_3.delete("1.0", tk.END)
txt_3.insert(tk.END, pdf_text)
so replace your CurSelect(evt) method with this:
def CurSelect(evt):
selected_indexes = list_1.curselection()
first_selected = selected_indexes[0]
pdf_text = pdfFiles[first_selected][2]
txt_3.delete("1.0", tk.END)
txt_3.insert(tk.END, pdf_text)

How to give button ID when calling a create button function

Please excuse my ignorance as I'm new to Python and am experimenting.
I am creating a dashboard that allows users to open a file. When a user opens a file, the software adds a row with the file name, progress bar and a few buttons all on the same row. The row creation is done by calling a function which increments the row value every time its called to ensure it doesn't replace the existing row but instead adds another row beneath it to represent the latest file opened by the user.
Lets say there are two rows on the dashboard with their respective file names and buttons. My problem is when I go to press the button on the first row, it thinks that I'm pressing the button on the second row simply because the counter is set to 2 when adding a second row and I am trying to identify which button the user is pressing by checking the row but am unable to do that due to the counter.
Is there anyway I can "bind" the row value to the button when declaring it when calling the create button function so that they keep some form of ID and I can tell which row the user is interacting with?
I have tried assigning the button an ID when calling the function to identify which button is being pressed and on what row, but the button gets overwritten on all rows when the function is called again.
I have also tried to check what button the user is pressing by checking the text of the button, but that also gets overwritten when the function is re-called regardless if the names of the buttons are different on the UI itself.
Any help is very much appreciated. Hopefully I was clear enough.
def OpenFile(self): # user opening file which creates row with button
print("Opening")
btnCreation(self)
def btnCreation(self): # function to create buttons dynamically
incrementFunction(self)
global EXEBtn
EXEBtn = Button(Main_frame,
text="Execute",
width=8,
command=self.Execute)
EXEBtn.grid(row=RowCounter, column=2, padx=3)
def incrementFunction(self):
global RowCounter
RowCounter = RowCounter + 1
def Execute(self):
# check which row the execute button was pressed on to do something
A simple and quick solution is just to give the function the current RowCounter value with a lambda expression like this:
def btnCreation(self): # function that creates button dynamically
incrementFunction(self)
global EXEBtn
EXEBtn = Button(Main_frame,
text="Execute",
width=8,
command=lambda e=RowCounter: self.Execute(e))
EXEBtn.grid(row=RowCounter, column=2, padx=3)
The specific change here is command=lambda e=RowCounter.get(): self.Execute(e), changing the way the buttons command works. Basically it is creating a lambda expression that saves the value of RowCounter, which will be passed to the Execute() function when the button is pressed.

Error appears when curselection bind to ListBox returns empty tuple after clicking in other widget

I have an issue with curselection of ListBox. It appears in Python 3.6. In Python 3.4 everything works fine. My code selects items from ListBox and put it in Entry widget. It works perfectly well when i click on the items in ListBox. But sometimes (after 5-10 clicks) when I click in Entry widget an error appears:
_tkinter.TclError: bad listbox index "": must be active, anchor, end, #x,y, or a number
Here's my code sample:
from tkinter import *
def insert_into_entry(event):
index=list_box.curselection()
print(index)
selected_item=list_box.get(index)
entry1.delete(0,END)
entry1.insert(END,selected_item)
window=Tk()
entry=Entry(window)
entry.grid(row=0,column=0)
list_box=Listbox(window,height=5,width=45)
list_box.grid(row=1,column=0)
list_box.bind('<<ListboxSelect>>',insert_into_entry)
entry1=Entry(window)
entry1.grid(row=2,column=0)
a=['one','two','three','four']
for i in a:
list_box.insert(END,i)
window.mainloop()
I tried to examine changes in index value. And error brings about when after clicking in Entry widget index returns empty tuple. It's my first question so I'll be greatfull for every response.
From what I can see by testing <<ListboxSelect>> I believe that the <<ListboxSelect>> event activates every time the selection changes even if that selection changes to "nothing selected". I believe this is why when you click off of the listbox and into any other field and the <<ListboxSelect>> event is triggered your insert_into_entry function is getting an empty tuple from curselection().
According to the documentation here the courselection() method will return an empty tuple if nothing is selected.
To avoid any errors we can first check if the results of ndex = list_box.curselection() are not equal to () an empty tuple. If not then perform the rest of the function. This should prevent any errors caused by this behaviour with the <<ListboxSelect>> event being called outside of the listbox.
Oh and one other thing. Don't use built in names for variable names. change index = list_box.curselection() to something like ndex = list_box.curselection()
from tkinter import *
window=Tk()
def insert_into_entry(Event = None):
ndex = list_box.curselection()
if ndex != ():
selected_item=list_box.get(ndex)
entry1.delete(0,END)
entry1.insert(END,selected_item)
entry=Entry(window)
entry.grid(row=0,column=0)
list_box=Listbox(window,height=5,width=45)
list_box.grid(row=1,column=0)
list_box.bind('<<ListboxSelect>>', insert_into_entry)
entry1=Entry(window)
entry1.grid(row=2,column=0)
a=['one','two','three','four']
for i in a:
list_box.insert(END,i)
window.mainloop()

Resources