changing order of items in tkinter listbox - python

Is there an easier way to change the order of items in a tkinter listbox than deleting the values for specific key, then re-entering new info?
For example, I want to be able to re-arrange items in a listbox. If I want to swap the position of two, this is what I've done. It works, but I just want to see if there's a quicker way to do this.
def moveup(self,selection):
value1 = int(selection[0]) - 1 #value to be moved down one position
value2 = selection #value to be moved up one position
nameAbove = self.fileListSorted.get(value1) #name to be moved down
nameBelow = self.fileListSorted.get(value2) #name to be moved up
self.fileListSorted.delete(value1,value1)
self.fileListSorted.insert(value1,nameBelow)
self.fileListSorted.delete(value2,value2)
self.fileListSorted.insert(value2,nameAbove)

Is there an easier way to change the order of items in a tkinter listbox than deleting the values for specific key, then re-entering new info?
No. Deleting and re-inserting is the only way. If you just want to move a single item up by one you can do it with only one delete and insert, though.
def move_up(self, pos):
""" Moves the item at position pos up by one """
if pos == 0:
return
text = self.fileListSorted.get(pos)
self.fileListSorted.delete(pos)
self.fileListSorted.insert(pos-1, text)

To expand on Tim's answer, it is possible to do this for multiple items as well if you use the currentselection() function of the tkinter.listbox.
l = self.lstListBox
posList = l.curselection()
# exit if the list is empty
if not posList:
return
for pos in posList:
# skip if item is at the top
if pos == 0:
continue
text = l.get(pos)
l.delete(pos)
l.insert(pos-1, text)
This would move all selected items up 1 position. It could also be easily adapted to move the items down. You would have to check if the item was at the end of the list instead of the top, and then add 1 to the index instead of subtract. You would also want to reverse the list for the loop so that the changing indexes wouldn't mess up future moves in the set.

Related

How can I display information on separate lines in my GUI? [duplicate]

I have a working Tkinter.Listbox object, but I want to set it up so that its elements can have carriage returns without having to somehow set up multiple linked items.
For instance, if I want to generate a selection pane with items that look like this..
# Here are four elements for the selector Listbox..
lb_items = ('mama', 'luigi', 'my birds', \
'this is a single element\n spanning two lines!')
# This generates and displays the selector window..
tk_selector = SingleSelect(lb_items, "TEST SELECTOR")
tk_selector.run_selector()
..it would be great if I could get the output to look like this mockup..
..instead of what it actually generates, which is this..
Listboxes seem to ignore '\n' and triple-quote strings with line-returns entirely; if \n is used, neither the characters nor the line break appears.
Is it possible to have individual, selectable Listbox elements that appear with line breaks?
I would also be satisfied with a word-wrap option, but after some looking, I couldn't find any such option in Listbox or Tk in general.
I could probably fake the effect by making multiline strings into multiple elements then setting it up to return the whole line if any of them are called, but it feels like an ordeal for something that could have a simple solution.
Like Bryan Oakley said, there is no native support in Listbox for carriage returns, so I tried building the 'fake' version I mentioned in the question, and it turns out that it isn't really that hard.
My solution is to parse each 'raw' string before inserting them into the Listbox, breaking strings into individual lines using splitlines, recording the number of lines and which indices in the Listbox's element roll correspond to which unbroken input string, and then selecting all the parts whenever the Listbox's selection changes using Listbox.bind('<<ListboxSelect>>', self._reselection_fxn).
There's an abridged and annotated sample below, or you can see my complete, working, even-more-heavily annotated code here.
class Multiline_Single_Selector(object):
## Go ahead and choose a better class name than this, too. :/
def __init__(self, itemlist, ..):
# ..
lb_splitlines = self._parse_strings(itemlist)
# ^ splits the raw strings and records their indices.
# returns the split strings as a list of Listbox elements.
self.my_Listbox.insert(0, *lb_splitlines)
# ^ put the converted strings into the Listbox..
self.my_Listbox.bind('<<ListboxSelect>>', self._reselect)
# ^ Whenever the Listbox selection is modifed, it triggers the
# <<ListboxSelect>> event. Bind _reselect to it to determine
# which lines ought to be highlighted when the selection updates.
# ..
def _parse_strings(self, string_list):
'''Accepts a list of strings and breaks each string into a series of lines,
logs the sets, and stores them in the item_roster and string_register attributes.
Returns the split strings to be inserted into a Listbox.'''
self.index_sets = index_sets = []
# ^ Each element in this list is a tuple containing the first and last
# Listbox element indices for a set of lines.
self.string_register = register = {}
# ^ A dict with a whole string element keyed to the index of the its
# first element in the Listbox.
all_lines = []
# ^ A list of every Listbox element. When a string is broken into lines,
# the lines go in here.
line_number = 0
for item in string_list:
lines = item.splitlines()
all_lines.extend(lines) # add the divided string to the string stack
register[line_number] = item
# ^ Saves this item keyed to the first Listbox element it's associated
# with. If the item is selected when the Listbox closes, the original
# (whole) string is found and returned based on this index number.
qty = len(lines)
if qty == 1: # single line item..
index_sets.append((line_number, line_number))
else: # multiple lines in this item..
element_range = line_number, line_number + qty - 1
# ^ the range of Listbox indices..
index_sets.extend([element_range] * qty)
# ^ ..one for each element in the Listbox.
line_number += qty # increment the line number.
return all_lines
def _reselect(self, event=None):
"Called whenever the Listbox's selection changes."
selection = self.my_Listbox.curselection() # Get the new selection data.
if not selection: # if there is nothing selected, do nothing.
return
lines_st, lines_ed = self.index_sets[selection[0]]
# ^ Get the string block associated with the current selection.
self.my_Listbox.selection_set(lines_st, lines_ed)
# ^ select all lines associated with the original string.
def _recall(self, event=None):
"Get the complete string for the currently selected item."
selection = self.my_Listbox.curselection()
if selection: # an item is selected!
return self.string_register[selection[0]]
return None # no item is selected.
If you adjust this code to match your own and drop it into an existing Listbox setup, it ought to let you simulate carriage returns. It's not the same as a native line-wrap or \n-parsing function directly in Listbox, but it does basically the same thing.
To get the whole string corresponding to the current selection, just bind _recall to whatever input or event you want to have return it. In this case, it returns None when no item is selected.
It's also kind of a lot of work considering the sophistication of the desired effect and it may not be appropriate to every situation. But at least you can do it.
It is not possible for a listbox item to be spread across more than one line or row.
This solution seems to me much easier:
Instead of trying:
lb_items = ('mama', 'luigi', 'my birds', \
'this is a single element\n spanning two lines!')
make two elements:
lb_items = ('mama', 'luigi', 'my birds', \
'this is a single element', 'spanning two lines!')

PyQT QListWidget: limit selection to x items , disable rubber-band selection

I have a QListWidget instance with the following properties:
self.listWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.listWidget.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.listWidget.setViewMode(QtGui.QListView.IconMode)
I want to be able to select max 3 items only by clicking on them and not by rubber-band selection. The forth click should deselect all previous selected items and select de current one. Rubber-band selection should be disabled.
I have done something like this with limited functionality of what I want to achieve: I must return a list of max 3 item.text()of items selected . Rubber-band selection usage may result in all items in the list being selected and returned as a list of items.text().
myapp.listWidget.itemSelectionChanged.connect(icons_selected)
def icons_selected():
L = [item.text() for item in myapp.listWidget.selectedItems()]
if L.__len__() <= 2 :
myapp.listWidget. setSelectionMode(QAbstractItemView.ExtendedSelection)
if L.__len__() >= 3:
myapp.listWidget. setSelectionMode(QAbstractItemView.SingleSelection)
return L
Rubber-Band selection works regardless and I can end up with all the items selected. Which is something that I don't want.
I can workaround this by manipulating the QAbstractItemView.NoSelection but I think it's messy.
I would rather be using something like setSelectionModel(QItemSelectionModel.NoUpdate) , but don't know how to use it, can't figure it out.
Rubber-Band selection on all items :not what I want
LE:
this is what I've done:
self.listWidget.itemSelectionChanged.connect(self.get_selected_icons_list)
self.listWidget.itemDoubleClicked.connect(self.restore_selection)
......
def get_selected_icons_list(self):
....
else:
if icons_filename_list.__len__() <= 2:
self.listWidget.setSelectionMode(QAbstractItemView.ExtendedSelection)
if icons_filename_list.__len__() >= 3:
self.listWidget.setSelectionMode(QAbstractItemView.NoSelection)
if not icons_filename_list:
return None
else:
return icons_filename_list
def restore_selection(self, *args):
self.listWidget.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.listWidget.clearSelection()
self.listWidget.setCurrentItem(args[0])

How do I check if any entry boxes are empty in Tkinter?

I am making a GUI in Tkinter with Python 2.7. I have a frame with about 30 entry boxes among other things. I don't have access to the code right now, but is there a way to check if any or several of the boxes are empty so I can warn the user and ask them if they want to proceed?
Is there a way to go through each of them with a 'for' loop and check the length of each entry. Or is there a command to check if any box is empty?
You can get the content of the entry using Tkinter.Entry.get method.
Check the length of the entry using len function and get method:
if len(entry_object.get()) == 0: # empty!
# do something
or more preferably:
if not entry_object.get(): # empty! (empty string is false value)
# do something
Use for loop to check several entry widgets:
for entry in entry_list:
if not entry_object.get():
# empty!
You should populate entry_list beforehand.
Manually:
entry_list = []
entry = Entry(...)
...
entry_list.append(entry)
entry = Entry(...)
...
entry_list.append(entry)
or, using winfo_children method (to get all entries):
entry_list = [child for child in root_widget.winfo_children()
if isinstance(child, Entry)]

How to properly insert text at the current position in Tkinter?

Question
How can I insert text into a Tkinter Textbox? I am trying to create a word processor which inserts lists at the current position.
What I have tried so far
I have tried to use the CURRENT argument, but this is unreliable.
def listcmd(self): #THIS HAS BUGS!!! FIX IT SOON
number = self.listentry.get()
number = int(number)
listINT = 1
for x in xrange(number):
self.write.insert(CURRENT, "%s:" % (str(listINT) )) #This is used for the number
self.write.insert(CURRENT, "\n") #This inserts the newline
listINT += 1
You need to use the index "insert" or Tkinter.INSERT. That always refers to the insertion cursor.

Tkinter Listbox not deleting items correctly

I have a method that is suppose to take a search parameter and remove everything from the list that does not meet the parameter. But when it runs it removes list items at almost random. I've debugged it and it correctly determines if an item needs to be removed but it doesn't remove the right one. I think it has something to do with when I remove one item it messes up the indexes of the rest of the list, which doesn't with with my method of tracking the index.
I posted the whole class but the relevant code is towards the bottom
class StudentFinderWindow(Tkinter.Toplevel):
def __init__(self):
Tkinter.Toplevel.__init__(self) # Create Window
##### window attributes
self.title('Edit Students') #sets window title
##### puts stuff into the window
# text
editStudentInfoLabel = Tkinter.Label(self,text='Select the student from the list below or search for one in the search box provided')
editStudentInfoLabel.grid(row=0, column=0)
# entry box
self.searchRepositoryEntry = Tkinter.Entry(self)
self.searchRepositoryEntry.grid(row=1, column=0)
# list box
self.searchResults = Tkinter.Listbox(self)
self.searchResults.grid(row=2, column=0)
# search results initial updater
self.getStudentList()
for student in self.studentList:
self.searchResults.insert(Tkinter.END, student)
##### event handler
self.searchRepositoryEntry.bind('<KeyRelease>', self.updateSearch)
This is the relevant code
def updateSearch(self, event):
parameters = self.searchRepositoryEntry.get()
int = 0
currentList = self.searchResults.get(0, Tkinter.END)
length = len(parameters)
print(parameters)
print(length)
for i in currentList:
if not i[0:length] == parameters:
self.searchResults.delete(int)
print(i[0:length] == parameters)
print(i[0:length])
print(int)
int += 1
def getStudentList(self):
global fileDirectory # gets the directory that all the files are in
fileList = listdir(fileDirectory) # makes a list of files from the directory
self.studentList = [] # makes a new list
for file in fileList: # for loop that adds each item from the file list to the student list
self.studentList.append(file[:-4])
When you delete an item, everything below it moves up causing the index of all following items to change. The simplest solution to this sort of a problem (it's also common when deleting words from a text widget) is to delete backwards, starting at the end.
I think you already know the problem. When you delete an item, the index for the rest of the items change. For example, if you delete the 4th item, then the 5th item becomes the "new" 4th item. So you don't want to increment int whenever you delete an item. You can implement that with continue:
for i in currentList:
if not i[0:length] == parameters:
self.searchResults.delete(int)
continue # <-- Use continue so `int` does not increment.
int += 1
PS. It's not good coding style to use int as a variable name -- in Python it masks the built-in function of the same name.

Categories