Is there a way to get and change the active row in a QTreeView (not QTreeWidget)? By active, I mean the row with the focus highlight, not the selected row. In the paint event, I can use QStyle.State_HasFocus to get the active row, but this doesn't seem to work elsewhere.
You can get/set the active row with the currentIndex() and setCurrentIndex() functions that you can find in both QTreeView and QItemSelectionModel (the latter is returned by QTreeView.selectionModel()).
And despite its name, the QItemSelectionModel handles the view current item, and the view selection independently.
Current item is the one which is indicated by the focus rectangle. You can change it using selectionModel function of the tree view. If you don't want to change currently selected items, pass QtGui.QItemSelectionModel.NoUpdate as a second parameter to setCurrentIndex method. Below is an example:
index = model.index(3, 0);
view.selectionModel().setCurrentIndex(index, QtGui.QItemSelectionModel.NoUpdate)
this should move current item to the item with index 3
hope this helps, regards
For me it got nothing here new to ask such question, why because simple; you can use Qt-Designer and create QTreeView and then create line edit, then link them using the action editor, then transform the UI file to Py file, then you will see how things work behind the scene.
You will find this if you try:
QtCore.QObject.connect(self.treeView, QtCore.SIGNAL(_fromUtf8("clicked(QModelIndex)")), self.test)
Related
I have disabled an option in an OptionMenu to fix something in the code where if I click on it just duplicates it, so I thought by disabling the option is an easy work around, but when the user may want to change to another option both the options are now disabled making the first one not usable again. I now need to return the option to normal. I thought of getting all the options which are disabled but couldn't figure out on how to do that. Sorry for the long para.
Any suggestions are useful.
Assume optmenu is the instance of OptionMenu, the following code will return all the items that are disabled:
# optmenu is the instance of OptionMenu
menu = optmenu['menu']
items = [] # list to hold all disabled items
# go through all option items
for i in range(menu.index('end')+1):
if menu.entrycget(i, 'state') == 'disabled':
items.append(menu.entrycget(i, 'label'))
print(items)
You might want to consider an object-oriented approach, defining for your object either a dict, list or some other array of settings, from which you can yourself easily fetch the status of any single control. Even if you don't want to do OOP, you should probably yourself save the current settings to an array.
When you want to select an item in a Treeview, you usually use the double-click:
def print_element(event):
print(my_treeview.selection()[0])
my_treeview.bind("<Double-1>", print_element)
Today I tried to do the same but using a single click instead:
my_treeview.bind("<Button-1>", print_element)
But it wouldn't work. The output was just an empty tuple.
I started to search online for an explanation... why is it not working?
EDIT:
My goal was actually to do something every time a treeview item was selected.
I proposed a solution myself using the identify() function of Tkinter
Another user proposed to use the Tkinter callback <ButtonRelease-1> which is much more appropriate
Finally, a third user focused his answer on using the Tkinter callback <<TreeviewSelect>>, which is for sure the best option
The reason it doesn't work the way you expect is because your custom single-click binding happens before the default behavior. So, when your single-click is processed, that happens before an item is selected. The second time you click, your function will print the previously selected item.
If you want to have a function called when an item is selected, you should bind to <<TreeviewSelect>>, which will fire immediately after the user selects an item with a single click or via the keyboard.
The default behavior of a treeview supports selecting multiple items at once, so the following code will print out the text of all of the selected items as a list, even if only a single item is selected. You can, of course, modify this to only print out the first selected item if you so desire.
def print_element(event):
tree = event.widget
selection = [tree.item(item)["text"] for item in tree.selection()]
print("selected items:", selection)
tree.bind("<<TreeviewSelect>>", print_element)
It is because the selection is not set yet when the <Button-1> (it is the same as <ButtonPress-1>, i.e. when the mouse button 1 is pressed and not released) event callback is called.
You should bind on <ButtonRelease-1> or <<TreeviewSelect>> instead as the selection is set when the event callback is being executed.
Why it doesn't work
When you click an item in a treeview, that item is still not in a SELECTED status in the moment the callback is activated. You are changing the status in that very moment.
Using a double-click, the first click change the status, and at the second click you are activating your callback, so the status has already been changed.
How can it work
Kudos to this website
In short,
def print_element(event):
print(my_treeview.identify('item', e.x, e.y))
my_treeview.bind("<Button-1>", print_element)
This time, print_element() will check the coordinates of the mouse, and will discover the selected item check what is under the mouse. Nice and clean!
I have an object of type QListWidget where the user can select 0 or more items.
If nothing is selected, how can i trigger an event that would call a function?
I know that you can detect clicking on a specific item by using:
QListWidget.itemClicked.connect(self.item_click)
Is there something similar for when nothing is selected at all? (or in other words, the QListWidget is clear)
Thanks!
Generally, you would connect to the itemSelectionChanged signal and then check whether anything is selected.
self.listwidget.itemSelectionChanged.connect(self.on_selection_changed)
def on_selection_changed(self):
if not self.listwidget.selectedItems():
# Do Stuff Here
self.nothing_selected_function()
But that will only catch events where something was selected and then the user deselected everything. If nothing was ever selected, it's not going to trigger this signal (like the first time you build the list, and nothing is selected). You'd have to call the slot manually in that case.
self.listwidget = ... # Code that builds and populates list widget
# Call this manually the first time.
self.on_selection_changed()
But part of your question is ambiguous. Why do you want to know when something is "not selected"? What about when a new item is added to the list? Should it trigger your "not selected" function since the list has changed, but there still isn't anything selected?
[I'm using PyQt4, but I think this Qt4 issue is not Python specific.]
I have a QTableWidget. In each row, the first column holds a button. When clicked, the row is removed.
To remove the row, I use removeRow(int row) method, which takes as argument the index of the row. When connecting the signal, I can't know the index of the row because it might change in the meantime (for instance if the first row is removed, all row indexes are changed).
The accepted answer here suggests to pass the callback an instance of a QTableWidgetItem in the line, then get the row number from this item at deletion time.
This would be nice, except none of the elements of the row is a QTableWidgetItem. The elements are the button itself and a few ComboBoxes.
I can't figure out a way around this.
Can I somehow fit one of my elements into a QTableWidgetItem? Should I add a QTableWidgetItem in some sort of hidden column?
Our current implementation uses indexAt(QtGui.qApp.focusWidget()) (see other answer to question mentioned above), which looks like a sorry workaround to me.
If I replace the button with a checkable QTableWidgetItem like this
rm_item = QtGui.QTableWidgetItem()
rm_item.setFlags(QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemIsEnabled)
I have a QTableWidgetItem I can use to get back to the row index. But I don't know how to catch a "checked" or "clicked" event from it like I do with the button. All I found is the itemClicked signal of QTableWidget, but then I'd have to filter all the other widgets out.
There has to be something obvious I'm missing.
Edit
From what I read here, I could add both a QTableWidgetItem with setItem and a Button widget with setCellWidget to the same cell. This doesn't seem so natural to me, but apparently it works (can't test right now).
I guess I'll do that. Add the Button, plus a dummy QTableWidgetItem on the same cell to pass as a reference to the row.
Is this how it is meant to be?
Edit 2
Or maybe QTableWidget is not the proper Widget and I should be using a Layout, as suggested here.
It seems that using a layout rather than a table is possibly the most "correct" answer, but that may come with it's own difficulties, as seen in my answer to this question:
How to delete widgets from gridLayout
If you want to continue using a table, a somewhat cleaner solution than adding dummy items would be to use a persistent model index:
button = QtGui.QPushButton(text, self.table)
self.table.setCellWidget(row, column, button)
index = QtCore.QPersistentModelIndex(
self.table.model().index(row, column))
button.clicked.connect(
lambda *args, index=index: self.handleButton(index))
def handleButton(self, index):
print('button clicked:', index.row())
if index.isValid():
self.table.removeRow(index.row())
If I understand your question correctly:
def set_button(self, row, col):
# create a push button
btn = QtGui.QPushButton('Remove row')
# connect to action
btn.clicked.connect(self.remove_row)
# set in cell
self.setCellWidget(row, col, btn)
def remove_row(self):
# find what is clicked
clicked = QtGui.qApp.focusWidget()
# position
idx = self.indexAt(clicked.pos())
# remove this row
self.removeRow(idx.row())
I have an application written in Python using PySide, the UI was created with QT Designer.
I have a list in the window, which has a set of items. When the application loads, it seems to grab the last item in the list (even though there is no 'highlighted' row showing in the list). If I break and check if there is a selected item at that time, I will get the last item in the list.
I have this code:
#QtCore.Slot(QTreeWidgetItem,QTreeWidgetItem)
def on_list_currentItemChanged(self,current=None,previous=None):
if current:
self.ui.actionDelete_Task.setEnabled(True)
else:
self.ui.actionDelete_Task.setEnabled(False)
Which determines if there is a current item selected. So upon initial load, I expect current to be empty, but it's not, it's pointing to the last item in the list, even though no item has been selected.
Is there something that I'm missing? Or, alternatively, how do I tell the list to not have a selected row/deselect all rows?
Ultimately what I'm looking for is (a) that when the form loads and the list is originally shown with no selected item, that the delete button is disabled and even (b) if someone should delete an item through this action, there won't be a selected item after that (so they couldn't just hit delete twice and delete two rows in sequence).
The current item and the selection are not necessarily the same thing. The current item refers to the item with the keyboard focus; the selection refers to the currently highlighted items. So a tree widget with no selection will still have a current item (unless it has no items at all).
So one solution might be to simply check that the current item is also selected:
#QtCore.Slot(QTreeWidgetItem, QTreeWidgetItem)
def on_list_currentItemChanged(self, current=None, previous=None):
if current is not None and current.isSelected():
self.ui.actionDelete_Task.setEnabled(True)
else:
self.ui.actionDelete_Task.setEnabled(False)
EDIT
After taking a look at the Qt source code, it appears the above code won't work because the currentItemChanged signal is emitted before the selection is reset.
So, instead, I would suggest using the itemSelectionChanged signal, and checking the selectedItems:
def on_list_ItemSelectionChanged(self):
if self.list.selectedItems():
self.ui.actionDelete_Task.setEnabled(True)
else:
self.ui.actionDelete_Task.setEnabled(False)
NB: taking this approach may also entail replacing the usage of currentItem() with selectedItems()[0] elsewhere in your code.