I want to implement a feature which will allow user to navigate in Gtk.TreeView widget by arrow keys, unfortunately select_iter() method is not doing what I was expecting from it, i. e. it fails to select parent node of selected node :P
And now I need explanation why it's not working or hint on some kind of workaround of this issue.
Below is ready to run test program which demonstrates this problem. Problematic line of code is tagged with #FIXME.
from gi.repository import Gtk
from gi.repository import Gdk
class WizardManager(Gtk.Dialog):
'''Dialog window which makes possible to choose type of resource to create by editor.'''
def __init__(self, parent):
super().__init__('Wizard manager', parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT)
self.set_default_response(Gtk.ResponseType.OK)
self.set_decorated(False)
self.set_size_request(640, 480)
vbox = self.get_content_area()
self.__tree_store = Gtk.TreeStore(str)
self.__tree_view = Gtk.TreeView(self.__tree_store)
self.__tree_view.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
self.__tree_view.connect('key-press-event', self.__on_tree_view_key_press)
self.__tree_view.set_headers_visible(False)
text_renderer = Gtk.CellRendererText()
text_column1 = Gtk.TreeViewColumn(None, text_renderer)
text_column1.add_attribute(text_renderer, 'text', 0)
self.__tree_view.append_column(text_column1)
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.add(self.__tree_view)
vbox.pack_start(scrolled_window, True, True, 0)
self.__populate_tree_store()
self.show_all()
def __on_tree_view_key_press(self, tree_view, event):
# TODO Implement tree navigation with arrow keys
tree_selection = tree_view.get_selection()
selected_iter = tree_selection.get_selected()[1]
if selected_iter:
selected_tree_path = self.__tree_store.get_path(selected_iter)
# Right arrow and Return should expand selected node.
if event.keyval == Gdk.KEY_Right or event.keyval == Gdk.KEY_Return:
tree_view.expand_row(selected_tree_path, False)
# Left arrow should collapse node or select it parent.
elif event.keyval == Gdk.KEY_Left:
if not tree_view.collapse_row(selected_tree_path):
# Unable to collapse node it must be empty. select it's parent.
parent_iter = selected_iter.copy()
if self.__tree_store.iter_parent(parent_iter):
# FIXME Why select_iter() executes without error and is not able to select parent node?
# same goes for select_path() :P
tree_selection.select_iter(parent_iter)
def __populate_tree_store(self):
# Ordinary resources
self.__tree_store.append(None, ('File',))
self.__tree_store.append(None, ('Directory',))
# Python files
python_dir = self.__tree_store.append(None, ('Python',))
self.__tree_store.append(python_dir, ('Python module',))
self.__tree_store.append(python_dir, ('Python package',))
# Django files
django_dir = self.__tree_store.append(python_dir, ('Django',))
self.__tree_store.append(django_dir, ('Django project',))
self.__tree_store.append(django_dir, ('Django app',))
if __name__ == '__main__':
app = Gtk.Window(Gtk.WindowType.TOPLEVEL)
app.connect('destroy', lambda a: Gtk.main_quit())
dlg = WizardManager(app)
dlg.run()
dlg.destroy()
Gtk.main()
Here you have a hint!
#! /usr/bin/python
###########################################################
#
# Basic Gtk.TreeView Example with two sortable columns
#
###########################################################
# use the new PyGObject binding
from gi.repository import Gtk
import os
import getpass # this is only to automatically print your home folder.
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title='My Window Title')
self.connect('delete-event', Gtk.main_quit)
# Gtk.ListStore will hold data for the TreeView
# Only the first two columns will be displayed
# The third one is for sorting file sizes as numbers
store = Gtk.ListStore(str, str, long)
# Get the data - see below
self.populate_store(store)
treeview = Gtk.TreeView(model=store)
# The first TreeView column displays the data from
# the first ListStore column (text=0), which contains
# file names
renderer_1 = Gtk.CellRendererText()
column_1 = Gtk.TreeViewColumn('File Name', renderer_1, text=0)
# Calling set_sort_column_id makes the treeViewColumn sortable
# by clicking on its header. The column is sorted by
# the ListStore column index passed to it
# (in this case 0 - the first ListStore column)
column_1.set_sort_column_id(0)
treeview.append_column(column_1)
# xalign=1 right-aligns the file sizes in the second column
renderer_2 = Gtk.CellRendererText(xalign=1)
# text=1 pulls the data from the second ListStore column
# which contains filesizes in bytes formatted as strings
# with thousand separators
column_2 = Gtk.TreeViewColumn('Size in bytes', renderer_2, text=1)
# Mak the Treeview column sortable by the third ListStore column
# which contains the actual file sizes
column_2.set_sort_column_id(1)
treeview.append_column(column_2)
# Use ScrolledWindow to make the TreeView scrollable
# Otherwise the TreeView would expand to show all items
# Only allow vertical scrollbar
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(
Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add(treeview)
scrolled_window.set_min_content_height(200)
self.add(scrolled_window)
self.show_all()
def populate_store(self, store):
directory = '/home/'+getpass.getuser()
for filename in os.listdir(directory):
size = os.path.getsize(os.path.join(directory, filename))
# the second element is displayed in the second TreeView column
# but that column is sorted by the third element
# so the file sizes are sorted as numbers, not as strings
store.append([filename, '{0:,}'.format(size), size])
# The main part:
win = MyWindow()
Gtk.main()
Related
im trying to create an UI with PyQt5 which has a tableWidget and a label that will display the text in every 4th column of the table, by order while the user scrolls through.
I cant seem to get the text in the selected cell from the table.. closest i got is this:
def open_csv_in_table (self):
f = open ("test.csv")
fData = csv.reader(f)
csvTable = list(fData)
self.tableWidget.setRowCount(len(csvTable))
self.tableWidget.setColumnCount(len(csvTable[0])-4)
for line in range( len(csvTable)):
for row in range(len(csvTable[0])):
self.tableWidget.setItem(line, row,QtWidgets.QTableWidgetItem(csvTable[line][row]))
self.tableWidget.setColumnWidth(0 , 10) # ID
self.tableWidget.setColumnWidth(1 , 150) # TEST NAME
self.tableWidget.setColumnWidth(2 , 50) # STATUS
self.tableWidget.setColumnWidth(3 , 300) # REMARKS
self.tableWidget.setColumnWidth(4 , 737) # LONG DESCRIPTION
def label_display(self):
self.label.setText(str(self.tableWidget.itemClicked))
print(str(self.tableWidget.itemClicked))
And im calling the display function with:
self.open_csv_in_table()
self.tableWidget.itemClicked.connect (lambda: self.label_display())
itemClicked is a signal that does not have the information of the item clicked so that is not the way to get that value. The signals what to do is pass the data through the arguments of the slot, in your case you must change to:
def label_display(self, item):
self.label.setText(item.text())
and
self.open_csv_in_table()
self.tableWidget.itemClicked.connect(self.label_display)
I've connected to the "key-press-event" to navigate through a Gtk.TreeView. I successfully got Tab to navigate to the right (across a row). I'm having trouble with using Return to navigate downwards. When a cell is selected I can edit its contents but I have to press Return once to commit the value and again to navigate to the cell below. I would like the behaviour to be like the Tab where I press Return once and the change is committed and the selected cell moves down one. I'm gunning for behaviour like that of a spreadsheet.
I'm guessing there is a conflict here with key bindings, i.e., the first Return press event commits the changes and the second Return press event navigates downwards. Also, I tried connecting the Shift_L key (instead of Return) to navigate downwards and while it navigates with one press it also fails to commit the changes to the cell.
I'll whip up a MWE if need be but I figure someone out there might know the issue here and could point me in the right direction or educate me.
EDIT: Okay, I took the time and stripped everything down to a MWE that can be tested/eyeballed by anyone able to help. The pertinent section of code is the callback function named onTreeNavigateKeyPress. Within that, the troublesome condition is the elif keyname == 'Return'. If you run this on your machine you'll see that you can change the cell value and Tab to the right and the Tab both navigates rightward and commits the changed value to the cell. Doing the same with the Return key will commit the change but you'll need to press Return again to navigate downwards. Call me pedantic but I hate that. As you can see from the code I have tried calling the Gtk.TreeView.set_cursor method directly with the new location as well as calling it from a new thread using Glib.timeout_add.
#!/usr/bin/env python3`
from gi.repository import Gtk, Gdk, GLib
class linFitApp(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title='Testing Keypress Events on Treeview')
self.set_position(Gtk.WindowPosition.CENTER)
self.set_default_size(400, 300)
self.set_border_width(5)
self.mainBox = Gtk.Box()
self.scrollTableWindow = Gtk.ScrolledWindow()
self.scrollTableWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.mainBox.pack_start(self.scrollTableWindow, False, False, 0)
self.add(self.mainBox)
############################################################################
#Now to set up the data table
self.dataTableListStore = Gtk.ListStore(float, float, float, float)
self.dataTableTreeView = Gtk.TreeView(model=self.dataTableListStore)
self.dataTableTreeView.props.activate_on_single_click = True
self.dataTableTreeView.connect("key-press-event", self.onTreeNavigateKeyPress)
#set up the x column
self.xColumnTextRenderer = Gtk.CellRendererText()
self.xColumnTextRenderer.set_property("editable", True)
self.xColumnTextRenderer.connect("edited", self.onXChanged)
self.xColumnTreeView = Gtk.TreeViewColumn("x", self.xColumnTextRenderer, text=0)
#set up the y column
self.yColumnTextRenderer = Gtk.CellRendererText()
self.yColumnTextRenderer.set_property("editable", True)
self.yColumnTextRenderer.connect("edited",self.onYChanged)
self.yColumnTreeView = Gtk.TreeViewColumn("y", self.yColumnTextRenderer, text=1)
#set up the dx column
self.dxColumnTextRenderer = Gtk.CellRendererText()
self.dxColumnTextRenderer.set_property("editable", True)
self.dxColumnTextRenderer.connect("edited",self.onDxChanged)
self.dxColumnTreeView = Gtk.TreeViewColumn("dx", self.dxColumnTextRenderer, text=2)
#set up the dy column
self.dyColumnTextRenderer = Gtk.CellRendererText()
self.dyColumnTextRenderer.set_property("editable", True)
self.dyColumnTextRenderer.connect("edited",self.onDyChanged)
self.dyColumnTreeView = Gtk.TreeViewColumn("dy", self.dyColumnTextRenderer, text=3)
#pack treeview into the scrolled window
self.scrollTableWindow.add(self.dataTableTreeView)
#add treeview columns to treeview
self.dataTableTreeView.append_column(self.xColumnTreeView)
self.dataTableTreeView.append_column(self.yColumnTreeView)
self.dataTableTreeView.append_column(self.dxColumnTreeView)
self.dataTableTreeView.append_column(self.dyColumnTreeView)
#fill in treeview with some sample data
self.dataTableListStore.append([0, 4, 0, 0])
self.dataTableListStore.append([5, 8.2, 0, 0])
self.dataTableListStore.append([10, 11.7, 0, 0])
self.dataTableListStore.append([15, 16.5, 0, 0])
self.dataTableListStore.append([20, 19, 0, 0])
self.dataTableListStore.append([25, 24.5, 0, 0])
self.dataTableListStore.append([30, 26.2, 0, 0])
#define the callbacks for cell editing
def onXChanged(self, widget, path, number):
self.dataTableListStore[path][0]=float(number.replace(',', '.'))
def onYChanged(self, widget, path, number):
self.dataTableListStore[path][1]=float(number.replace(',', '.'))
def onDxChanged(self, widget, path, number):
self.dataTableListStore[path][2]=float(number.replace(',', '.'))
def onDyChanged(self, widget, path, number):
self.dataTableListStore[path][3]=float(number.replace(',', '.'))
#define the callback for keypress events
def onTreeNavigateKeyPress(self, treeview, event):
keyname = Gdk.keyval_name(event.keyval)
path, col = treeview.get_cursor()
columns = [c for c in treeview.get_columns()]
colnum = columns.index(col)
if keyname == 'Tab':
if colnum + 1 < len(columns):
next_column = columns[colnum + 1]
else:
next_column = columns[0]
GLib.timeout_add(50,
treeview.set_cursor,
path, next_column, True)
elif keyname == 'Return':
model = treeview.get_model()
#Check if currently in last row of Treeview
if path.get_indices()[0] + 1 == len(model):
path = treeview.get_path_at_pos(0,0)[0]
#treeview.set_cursor(path, columns[colnum], True)
GLib.timeout_add(50,
treeview.set_cursor,
path, columns[colnum], True)
else:
path.next()
#treeview.set_cursor(path, columns[colnum], True)
GLib.timeout_add(50,
treeview.set_cursor,
path, columns[colnum], True)
else:
pass
#create main application window and start Gtk loop
mainWindow = linFitApp()
mainWindow.connect("delete-event", Gtk.main_quit)
mainWindow.show_all()
Gtk.main()
The answer (maybe just a functional hack) is as follows:
Pressing Return while a Gtk.Treeview cell is in editing mode does not release the key-press-event signal. Instead of connecting my callback function to the key-press-event signal I connect it to the key-release-event signal which is emitted after Return is pressed while a cell is being edited. So pressing the Return key will activate whichever signal it does to commit a new value (I still don't know which signal that is) and releasing the Return key will activate the navigation to the next cell. Voila, we have the two desired actions resulting from a single press of the Return key.
Short answer:
Change this
self.dataTableTreeView.connect("key-press-event", self.onTreeNavigateKeyPress)
to this
self.dataTableTreeView.connect("key-release-event", self.onTreeNavigateKeyPress)
Used: python 2.79, gtk 2.0
Dear python and PyGtk-users
In my program I want to give the opportunity to show searchresults of a great datafile in a gtk.TreeView using the method in the code. In the function that creates the ListStore I also create the csv-file the store is using. Now I can see, that the csv-file has changed after a new search with different keywords, but the TreeView does not, it still keeps the result of the first search, though I clear the store everytime self.search is running. It's really enerving if you need to restart the program for every new search...
I already tried some things, like creating a proper function for the datacreatin for the store, but nothing serves and the truth is that I don't have a lot of ideas...
And everything I found in the internet about this was about autorefreshing of the store with a timer or other topics more profound, seems like everyone instead of me knows how to do this...
Can anyone tell me, what mistake I am making? How do I refresh a TreeView?(as you can see self.search is called by a button...) here the relevant part of the code:
import gtk, csv
class BASE:
#...
#some other funtions, irrelevant for the problem
#...
def search(self, widget, event, data=None):
#...
#all the searchingstuff and the creation of 'inwrite.csv'
#...
with open('/home/emil/Documents/inwrite.csv', 'r') as storefile:
lines = storefile.readlines()
store = gtk.ListStore(str, str, str, str, str, str)
store.clear()
i=0
while i < len(lines):
line = [lines[i]]
csvfile = csv.reader(line, delimiter=',')
for row in csvfile:
temp = (row[0], row[1], row[2], row[3], row[4], row[5])
store.append(temp)
i = i + 1
self.tabview = gtk.TreeView(store)
self.tabview.set_rules_hint(True)
self.tabview.set_reorderable(True)
self.sw.add(self.tabview)
self.tabview.show()
self.create_columns(self.tabview)
def __init__(self):
#...
#creating the Window, entries,...
#...
self.button1 = gtk.Button('Buscar')
self.button1.connect('clicked', self.search, 'button 1')
#...
#packing and showing the whole GUI
#...
def create_columns(self, tabview):
rendererText = gtk.CellRendererText()
rendererText.props.wrap_width = 80
rendererText.props.wrap_mode = gtk.WRAP_WORD
column = gtk.TreeViewColumn('Codigo', rendererText, text=0)
column.set_sort_column_id(0)
self.tabview.append_column(column)
#...
#creating 5 more columns by the same principle
#...
def main(self):
gtk.main()
if __name__=='__main__':
base = BASE()
run = base
run.main()
thanks for your help, tell me if you need more information!
I do not know what is exactly wrong with your program (if you want to know that, you should provide a working minimal example).
Nevertheless, you do not need to manually update the TreeView because the TreeView always shows the content of the ListStore or TreeStore. You also do not have to create the ListStore everytime. You can create it one time and clear and insert the new entries when the event is emitted.
Here is a small example that shows how it works:
#from gi.repository import Gtk # uncomment to use PyGObject
import gtk as Gtk
class TestCase:
def __init__(self):
win = Gtk.Window()
button = Gtk.Button('Show')
button.connect('clicked', self.on_button_clicked)
self.clicked = 0
self.liststore = Gtk.ListStore(int)
self.liststore.append((self.clicked,))
view = Gtk.TreeView(self.liststore)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn('demo', renderer, text=0)
view.append_column(column)
box = Gtk.HBox()
box.pack_start(button, True, True, 0)
box.pack_start(view, True, True, 0)
win.add(box)
win.connect('delete-event', Gtk.main_quit)
win.show_all()
def on_button_clicked(self, button):
self.clicked += 1
self.liststore.clear()
self.liststore.append((self.clicked,))
if __name__ == "__main__":
TestCase()
Gtk.main()
It also seems that you first create your csv file and read it afterwards. I don't know about the rest of your program but it is probably better to use the data directly and insert it into the ListStore and not read it from the csv file. It might be even possible then to just remove and insert the new entries and not clear the whole ListStore and insert everything again. That would be more efficient with bigger amounts of data.
I need to store items in a Gtk TreeView and when interacting with this TreeView, the user will can select one or more items in the list.
Because I'm new to GTK, I managed to populate the treeview and display a checkbox as the code below shows. But when I try to select, nothing happens and I do not know how to make this possible.
This is my Code:
# the column is created
renderer_products = gtk.CellRendererText()
column_products = gtk.TreeViewColumn("Products", renderer_products, text=0)
# and it is appended to the treeview
view.append_column(column_products)
# the column checkbox is created
renderer_checkbox = gtk.CellRendererToggle()
column_checkbox = gtk.TreeViewColumn("Selected", renderer_checkbox, text=0)
# and it is appended to the treeview
view.append_column(column_checkbox)
If you want to select the whole row and something happen:
#double click or not double click use
Gtk.TreeView.set_activate_on_single_click (bool)
#connect the treeview
treeview.connect ("row-activated", on_row_activate)
#inside the callback
def on_row_activate (treeview, path, column):
model = treeview.get_model ()
iter = treeview.get_iter (path)
yourdata = model[iter][model_index]
#do whatever with yourdata
If you want when you click the toggle and something happen:
#connect the renderer
renderer_checkbox.connect ("toggled", on_selected_toggled)
#inside the callback
def on_selected_toggled (renderer, path):
#modify the model or get the value or whatever
I'm having some problem sorting the items in my QListView using values in a field I specified.
Basically what I'm trying to do is this:
Detect faces in a collection of photos and display them in a QListView
Cluster the faces (images)
Update the view by placing items in the list (which are face images) belonging to the same cluster in together. Concretely, if item 1, 3, 5 are in one cluster and items 2, 4, 6 are in another, then items 1, 3, 5 should be displayed (in whatever permutations) before any of items 2, 4, 6 are displayed or vice versa.
The way I went about doing this is to set one of the UserRole field for each QStandardItem in my list to the cluster label and then try to get the QStandardModel to sort according to this UserRole. This would then display items in the same cluster (i.e. with the same cluster label in the UserRole) next to each other.
I'm able to set the UserRole successfully for the items but calling the sort function on the QStandardModel did not sort the items even though when I set the sort role to be the default DisplayRole (i.e. sort according to the text label of each face) it worked as intended.
Can anyone tell me what is wrong with my code or offer an alternative method? I've googled sorting list and I found the following link on QSortFilterProxyModel but as I'm quite new to Qt, I'm not able to adapt it to my situation.
Thanks in advance to any replies.
Here is the relevant code:
import os
from PySide.QtGui import QListView, QStandardItemModel, QStandardItem, QIcon
from PySide.QtCore import Qt
class FacesView(QListView):
"""
View to display detected faces for user to see and label.
"""
UNCLUSTERED_LABEL = -1
CLUSTER_ROLE = Qt.UserRole + 1
def __init__(self, *args):
super(FacesView, self).__init__(*args)
self._dataModel = QStandardItemModel()
self.setModel(self._dataModel)
# Layout items in batches instead of waiting for all items to be
# loaded before user is allowed to interact with them.
self.setLayoutMode(QListView.Batched)
def updateFaceClusters(self, labels):
"""Update the cluster label for each face.
#param labels: [1 x N] array where each element is an integer
for the cluster the face belongs to."""
assert(len(labels) == self._dataModel.rowCount())
# Put the cluster label each item/face belong to in the
# CLUSTER_ROLE field.
for i in xrange(self._dataModel.rowCount()):
index = self._dataModel.index(i, 0)
self._dataModel.setData(index, labels[i], self.CLUSTER_ROLE)
# Use cluster label as sort role
self._dataModel.setSortRole(self.CLUSTER_ROLE)
# This does NOT seem to sort the items even though it works fine
# when sort role is the default Qt.DisplayRole.
self._dataModel.sort(0)
print("Finished updating face clusters")
def itemsInList(self):
"""Returns the label for a face and the path to its image.
#return: (label, path)"""
items = []
for i in xrange(self._dataModel.rowCount()):
label = self._dataModel.index(i, 0).data(Qt.DisplayRole)
imagePath = self._dataModel.index(i, 0).data(Qt.UserRole)
clusterLabel = self._dataModel.index(i, 0).data(self.CLUSTER_ROLE)
items.append((imagePath, label, clusterLabel))
return items
def addItem(self, label, imagePath):
"""Add an item to list view
#param label: The label associated with the item.
#param imagePath: Path to image for the icon."""
if os.path.exists(imagePath):
icon = QIcon(imagePath)
else:
icon = QIcon(':/res/Unknown-person.gif')
item = QStandardItem(icon, label)
item.setEditable(True)
# Add image path to the UserRole field.
item.setData(imagePath, Qt.UserRole)
# Add cluster label to image. CLUSTER_ROLE is where I intend
# to put the item's cluster label.
item.setData(self.UNCLUSTERED_LABEL, self.CLUSTER_ROLE)
# Prevent an item from dropping into another item.
item.setDropEnabled(False)
# Add item to list indirectly by adding it to the model.
self._dataModel.appendRow(item)
def clear(self):
self._dataModel.clear()
There's nothing wrong with the code you posted. So there must be something wrong with how you are using it. How are you generating the cluster labels?
Here's a test script using your FacesView class that sorts as you intended:
from random import randint
from PySide.QtGui import QWidget, QPushButton, QVBoxLayout, QApplication
from facesview import FacesView
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.list = FacesView(self)
self.button = QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.list)
layout.addWidget(self.button)
def handleButton(self):
labels = []
self.list.model().setRowCount(0)
for row in range(10):
labels.append(randint(0, 3))
text = 'Item(%d) - Cluster(%d)' % (row, labels[-1])
self.list.addItem(text, 'icon.png')
self.list.updateFaceClusters(labels)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())