Python Tkinter Autocomplete combobox with LIKE search? - python-2.7

I am trying to populate a Tkinter combobox with pre-defined values to select from. It is populating and I am able to type in and get suggestions. However, in order to do this I have to definitely know the first few characters. If I know some text in the middle or end of the string, its of no use because the combobox does only a 'LIKE%' search and not a '%LIKE%' search.
Expected Output (Typing the word "Ceramic" fetches all names containing the string. Note: This is not a Tkinter screenshot):
This is my adaptation of the code till now, if anyone can suggest how to modify the AutocompleteCombobox class to do a LIKE search, it would be great.
The below working piece of code, as an example, has values "Cranberry" and "Strawberry" , my requirement is to type "berry" and get suggestions of both fruits.
import Tkinter
import ttk
import sqlite3
class AutocompleteCombobox(ttk.Combobox):
def set_completion_list(self, completion_list):
"""Use our completion list as our drop down selection menu, arrows move through menu."""
self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list
self._hits = []
self._hit_index = 0
self.position = 0
self.bind('<KeyRelease>', self.handle_keyrelease)
self['values'] = self._completion_list # Setup our popup menu
def autocomplete(self, delta=0):
"""autocomplete the Combobox, delta may be 0/1/-1 to cycle through possible hits"""
if delta: # need to delete selection otherwise we would fix the current position
self.delete(self.position, Tkinter.END)
else: # set position to end so selection starts where textentry ended
self.position = len(self.get())
# collect hits
_hits = []
for element in self._completion_list:
if element.lower().startswith(self.get().lower()): # Match case insensitively
_hits.append(element)
# if we have a new hit list, keep this in mind
if _hits != self._hits:
self._hit_index = 0
self._hits=_hits
# only allow cycling if we are in a known hit list
if _hits == self._hits and self._hits:
self._hit_index = (self._hit_index + delta) % len(self._hits)
# now finally perform the auto completion
if self._hits:
self.delete(0,Tkinter.END)
self.insert(0,self._hits[self._hit_index])
self.select_range(self.position,Tkinter.END)
def handle_keyrelease(self, event):
"""event handler for the keyrelease event on this widget"""
if event.keysym == "BackSpace":
self.delete(self.index(Tkinter.INSERT), Tkinter.END)
self.position = self.index(Tkinter.END)
if event.keysym == "Left":
if self.position < self.index(Tkinter.END): # delete the selection
self.delete(self.position, Tkinter.END)
else:
self.position = self.position-1 # delete one character
self.delete(self.position, Tkinter.END)
if event.keysym == "Right":
self.position = self.index(Tkinter.END) # go to end (no selection)
if len(event.keysym) == 1:
self.autocomplete()
# No need for up/down, we'll jump to the popup
# list at the position of the autocompletion
def test(test_list):
"""Run a mini application to test the AutocompleteEntry Widget."""
root = Tkinter.Tk(className='AutocompleteCombobox')
combo = AutocompleteCombobox(root)
combo.set_completion_list(test_list)
combo.pack()
combo.focus_set()
# I used a tiling WM with no controls, added a shortcut to quit
root.bind('<Control-Q>', lambda event=None: root.destroy())
root.bind('<Control-q>', lambda event=None: root.destroy())
root.mainloop()
if __name__ == '__main__':
test_list = ('apple', 'banana', 'Cranberry', 'dogwood', 'alpha', 'Acorn', 'Anise', 'Strawberry' )
test(test_list)

I suspect you need
if self.get().lower() in element.lower():
instead of
if element.lower().startswith(self.get().lower()):
to get data like with %LIKE% in database
But I don't know if you get good effect because this Combobox replaces text with suggestion so if you type be then it finds Cranberry and put in place be and you can't write ber.
Maybe you should display Cranberry as separated (dropdown) list, or popup tip.
Or maybe you will have to use string.find() to highlight correct place in Cranberry and continue to type ber in correct place.
EDIT: example how to use Entry and Listbox to display filtered list
In listbox_update I added sorting list (comparing lower case strings)
#!/usr/bin/env python3
import tkinter as tk
def on_keyrelease(event):
# get text from entry
value = event.widget.get()
value = value.strip().lower()
# get data from test_list
if value == '':
data = test_list
else:
data = []
for item in test_list:
if value in item.lower():
data.append(item)
# update data in listbox
listbox_update(data)
def listbox_update(data):
# delete previous data
listbox.delete(0, 'end')
# sorting data
data = sorted(data, key=str.lower)
# put new data
for item in data:
listbox.insert('end', item)
def on_select(event):
# display element selected on list
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print('---')
# --- main ---
test_list = ('apple', 'banana', 'Cranberry', 'dogwood', 'alpha', 'Acorn', 'Anise', 'Strawberry' )
root = tk.Tk()
entry = tk.Entry(root)
entry.pack()
entry.bind('<KeyRelease>', on_keyrelease)
listbox = tk.Listbox(root)
listbox.pack()
#listbox.bind('<Double-Button-1>', on_select)
listbox.bind('<<ListboxSelect>>', on_select)
listbox_update(test_list)
root.mainloop()
At start with full list
Later only with filtered items
EDIT: 2020.07.21
If you want to use <KeyPress> then you have to change on_keyrelease and use event.char, event.keysym and/or event.keycode because KeyPress is executed before tkinter update text in Entry and you have to add event.char to text in Entry (or remove last char when you press backspace)
if event.keysym == 'BackSpace':
value = event.widget.get()[:-1] # remove last char
else:
value = event.widget.get() + event.char # add new char at the end
It may need other changes for other special keys Ctrl+A, Ctrl+X, Ctrl+C, Ctrl+E, etc. and it makes big problem.
#!/usr/bin/env python3
import tkinter as tk
def on_keypress(event):
print(event)
print(event.state & 4) # Control
print(event.keysym == 'a')
# get text from entry
if event.keysym == 'BackSpace':
# remove last char
value = event.widget.get()[:-1]
elif (event.state & 4): # and (event.keysym in ('a', 'c', 'x', 'e')):
value = event.widget.get()
else:
# add new char at the end
value = event.widget.get() + event.char
#TODO: other special keys
value = value.strip().lower()
# get data from test_list
if value == '':
data = test_list
else:
data = []
for item in test_list:
if value in item.lower():
data.append(item)
# update data in listbox
listbox_update(data)
def listbox_update(data):
# delete previous data
listbox.delete(0, 'end')
# sorting data
data = sorted(data, key=str.lower)
# put new data
for item in data:
listbox.insert('end', item)
def on_select(event):
# display element selected on list
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print('---')
# --- main ---
test_list = ('apple', 'banana', 'Cranberry', 'dogwood', 'alpha', 'Acorn', 'Anise', 'Strawberry' )
root = tk.Tk()
entry = tk.Entry(root)
entry.pack()
entry.bind('<KeyPress>', on_keypress)
listbox = tk.Listbox(root)
listbox.pack()
#listbox.bind('<Double-Button-1>', on_select)
listbox.bind('<<ListboxSelect>>', on_select)
listbox_update(test_list)
root.mainloop()
BTW:
You can also use textvariable in Entry with StringVar and trace which executes function when StringVar changes content.
var_text = tk.StringVar()
var_text.trace('w', on_change)
entry = tk.Entry(root, textvariable=var_text)
entry.pack()
#!/usr/bin/env python3
import tkinter as tk
def on_change(*args):
#print(args)
value = var_text.get()
value = value.strip().lower()
# get data from test_list
if value == '':
data = test_list
else:
data = []
for item in test_list:
if value in item.lower():
data.append(item)
# update data in listbox
listbox_update(data)
def listbox_update(data):
# delete previous data
listbox.delete(0, 'end')
# sorting data
data = sorted(data, key=str.lower)
# put new data
for item in data:
listbox.insert('end', item)
def on_select(event):
# display element selected on list
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print('---')
# --- main ---
test_list = ('apple', 'banana', 'Cranberry', 'dogwood', 'alpha', 'Acorn', 'Anise', 'Strawberry' )
root = tk.Tk()
var_text = tk.StringVar()
var_text.trace('w', on_change)
entry = tk.Entry(root, textvariable=var_text)
entry.pack()
listbox = tk.Listbox(root)
listbox.pack()
#listbox.bind('<Double-Button-1>', on_select)
listbox.bind('<<ListboxSelect>>', on_select)
listbox_update(test_list)
root.mainloop()

Related

How can I save multiple entries to a list and then print a random entry from that list?

Hello i can only seem to print letters from an entry not the full entry (sentence).
I simply want to click a button using tkinter and after typing a sentence into a box, the button will store the sentences in a list.
Then I want to create a second button that will then print a random sentence from that list.
When I try this it only prints letters of the stored sentence.
Any ideas would be greatly appreciated as I’ve looked around for the last two days before asking.
Best wishes to you all
from tkinter import *
import random
def super_function():
out = map(Entry.get, entr)
clear_entry_1()
def clear_entry_1():
Entry_field.delete(0,END)
def super_function_2():
print(random.choice(entr))
root = Tk()
root.geometry('400x400')
entr = []
for i in range(10):
entr.append(Entry(root))
Entry_field = Entry()
Entry_field.pack()
Button1 = Button(root, text = 'Add your idea!', command =
super_function)
Button1.pack()
Button2 = Button(root, text='Generate an idea!',
command=super_function_2)
Button2.pack()
root.mainloop()
The for loop is useless and should be removed as it just appends 10 invisible entries.
What you need is to append the sentence inside super_function() instead:
def super_function():
# save the sentence
entr.append(Entry_field.get())
clear_entry_1()

Change text colour in Tkinter combobox, based on origin

I have two lots of computer names found in AD, all of which are sorted together and entered into a tkinter drop-down combobox. I would like to be able to change the text colour of the entries in the drop-down depending on which original list of computers it came from.
OPSpclist = []
OPS_pcs = active_directory.AD_object ("LDAP://OU=Locations - ...")
for OPSpc in OPS_pcs.search (objectCategory='Computer'):
OPSpc = str(OPSpc).upper()
OPSpc = OPSpc.split(",")[0].split("=")[1]
OPSpclist.append(OPSpc)
OSpclist = []
OS_pcs = active_directory.AD_object ("LDAP://OU=Locations - ...")
for OSpc in OS_pcs.search (objectCategory='Computer'):
OSpc = str(OSpc).upper()
OSpc = OSpc.split(",")[0].split("=")[1]
OSpclist.append(OSpc)
bothSchools = sorted(OSpclist) + sorted(OPSpclist)
optionList = sorted(bothSchools)
var1 = StringVar()
var1.set(optionList[0])
pcnameEntry = ttk.Combobox(entryframe, textvariable = var1, values = optionList, width=25)
pcnameEntry.focus_set()
pcnameEntry.grid(row=1, column=0, sticky=W, pady=(0, 10), padx=5)
Is it possible to have the items from the first list to appear in a different colour, all within the same, sorted, combobox drop-down list?
Thanks,
Chris.
Yes this is possible, the drop down is a listbox and therefore the items can be configured separately with the itemconfigure method. However, I don't know how to retrieve the combobox's listbox via Python but this can be done through tcl commands:
import Tkinter as tk
import ttk
root = tk.Tk()
l1 = [(name, 'computer 1') for name in 'ABCD']
l2 = [(name, 'computer 2') for name in 'ACEG']
l = sorted(l1 + l2)
combo = ttk.Combobox(root, values=[name for name, computer in l])
combo.pack()
combo.update_idletasks()
# I need to display the drop down once before setting the items' colors otherwise
# I get an error telling me the items don't exist, so I generate a click on the combobox button
combo.event_generate('<1>', x=combo.winfo_width() - 4, y=1)
colors = {'computer 1': 'blue', 'computer 2': 'red'}
# define the tcl variable corresponding to the drop down listbox
combo.tk.eval('set popdown [ttk::combobox::PopdownWindow %s]' % combo)
for i, (name, computer) in enumerate(l):
# set the color of each item (the background color can be changed too by adding '-background <color>')
combo.tk.eval('$popdown.f.l itemconfigure %i -foreground %s' % (i, colors[computer]))
root.mainloop()

How do I keep dynamic Entry fields populated after the function that created them terminates?

I'm working on a GUI to automate part of my project's pipeline using Tkinter and ttk in Python 2.7. I have a main Tk() window that generates a Toplevel() window upon clicking "Auto-Detect", then creates a dynamic series of readonly Entry widgets on button click based on a list of molecular species defined elsewhere in the code.
My issue is that, while the Entry boxes do appear correctly based on the species detected, they do not stay populated with the species names once the function that created them terminates. I suspect this is because the widgets aren't global [or even within the scope of the Tk() window], but rather are defined only within the function. However, I can't create them within the block of code where the Tk() window is defined since the number of Entry boxes needed is unknown until the button is pressed (thus calling the function that creates them).
I've included an abstracted block of code below that shows the problem I'm having. Apologies if it is long. I tried to cut it down as much as possible. The comments I included in the code show my thoughts and guesses for what's going on. It should be ready to run in Python 2.7; my hope is that the only Python 3.x changes that are necessary are import modifications.
My question is, after I have dynamically created Entry widgets within a function that is called by the main Tk() window and populated them with text, how can I prevent them from depopulating when the function ends? Disclaimer: I'm not a programmer (or even in the field of computer science) by trade, so I will do my best to hang on to all technical details, but I may have to ask some dumb questions.
from Tkinter import *
import ttk
from time import sleep
def update_list(manager, rows):
species_list = ['A','B','C','D']
del rows[1:]
for widget in manager.children.values():
widget.grid_forget()
if not species_list == ['']:
species_elem_list = []
for i, each in enumerate(species_list):
## Here I attempt to create a dynamic list of StringVars to attach to the Entry fields below, based on the contents of the species list.
species_elem_list.append(StringVar())
## Here I initialize the values of the elements of the Entry fields by setting the StringVar of each.
species_elem_list[i].set(each)
## I tried to attach the value of the StringVar (from the species list) to the Entry below, but when the program is run, the Entry does not stay populated.
temp_name = ttk.Entry(manager, textvariable=species_elem_list[i], state='readonly')
temp_color = ttk.Label(manager, text='data')
temp_row = [temp_name, temp_color]
rows.append(temp_row)
for row_number in range(len(rows)):
for column_number, each in enumerate(rows[row_number]):
each.grid(column=column_number, row=row_number)
each.grid()
manager.update()
sleep(3) ## Included so that the population of the fields can be observed before they depopulate.
## After this point, the update_list function terminates.
## When that happens, the Entry fields depopulate. How can I get them to persist after the function terminates?
root = Tk()
manager = ttk.Frame(root, padding='4 5 4 4')
manager.grid(column=0, row=0, sticky=NSEW)
name_label = ttk.Label(manager, text='Name')
color_label = ttk.Label(manager, text='RGB')
rows = [[name_label, color_label]]
options = ttk.Frame(root)
options.grid(sticky=NSEW)
detect_button = ttk.Button(options, text='Auto-Detect', command=lambda: update_list(manager,rows))
done_button = ttk.Button(options, text='Done', command=root.destroy)
detect_button.grid(column=0, row=0)
done_button.grid(column=1, row=0)
root.mainloop()
Ideally, the Entry widgets will remain (and remain populated!) after the function update_list terminates. I also want to be able to interact with the contents of these widgets from outside the function.
Currently, the Entry fields populate during the course of the function update_list, then depopulate immediately once it ends. I suspect this is because the widgets and their contents are not global in scope.
In textvariable=species_elem_list you use local variable species_elem_list which stop exist when you exit update_list()
You have to create species_elem_list outside update_list() and use global species_elem_list in update_list() to use global variable instead of local one when you do species_elem_list = []
from Tkinter import *
import ttk
from time import sleep
species_elem_list = [] # <--- put here or below of update_list
def update_list(manager, rows):
global species_elem_list # <-- use global variable instead of local one
species_list = ['A','B','C','D']
del rows[1:]
for widget in manager.children.values():
widget.grid_forget()
if not species_list == ['']:
species_elem_list = []
for i, each in enumerate(species_list):
## Here I attempt to create a dynamic list of StringVars to attach to the Entry fields below, based on the contents of the species list.
species_elem_list.append(StringVar())
## Here I initialize the values of the elements of the Entry fields by setting the StringVar of each.
species_elem_list[i].set(each)
## I tried to attach the value of the StringVar (from the species list) to the Entry below, but when the program is run, the Entry does not stay populated.
temp_name = ttk.Entry(manager, textvariable=species_elem_list[i], state='readonly')
temp_color = ttk.Label(manager, text='data')
temp_row = [temp_name, temp_color]
rows.append(temp_row)
for row_number in range(len(rows)):
for column_number, each in enumerate(rows[row_number]):
each.grid(column=column_number, row=row_number)
each.grid()
manager.update()
sleep(3) ## Included so that the population of the fields can be observed before they depopulate.
## After this point, the update_list function terminates.
## When that happens, the Entry fields depopulate. How can I get them to persist after the function terminates?
root = Tk()
manager = ttk.Frame(root, padding='4 5 4 4')
manager.grid(column=0, row=0, sticky=NSEW)
name_label = ttk.Label(manager, text='Name')
color_label = ttk.Label(manager, text='RGB')
rows = [[name_label, color_label]]
options = ttk.Frame(root)
options.grid(sticky=NSEW)
detect_button = ttk.Button(options, text='Auto-Detect', command=lambda: update_list(manager,rows))
done_button = ttk.Button(options, text='Done', command=root.destroy)
detect_button.grid(column=0, row=0)
done_button.grid(column=1, row=0)
root.mainloop()

PyQt 4. I can't delete content of QLineEdit()-object

I wrote an executable example - you can test it. When you start this program you will get three QPushButton()-objects and one QLineEdit()-object. There you can install or deinstall/uninstall the event filter or close the application. Please install the event filter and type a text. You will see what I want. I want the example program to protect the space key. In this current version the user can't press the space key more than 2 times. This program does work.
But I have a little problem. When I write a text in the QLineEdit()-object and then I highlight the text and I press the delete or return key, nothing happens. I am not able to delete the text. I am also not able to copy the marked text.
Whats wrong with the code below?
#!/usr/bin/env python
import sys
from PyQt4.QtCore import QEvent, Qt
from PyQt4.QtGui import QMainWindow, QWidget, QApplication, QVBoxLayout, QLineEdit, QPushButton
class Window(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.count_space_pressed = 0
self.current_pos = None
self.init_ui()
self.init_signal_slot_push_button()
def init_ui(self):
centralwidget = QWidget(self)
self.input_line_edit = QLineEdit(self)
self.close_push = QPushButton(self)
self.close_push.setEnabled(False)
self.close_push.setText("Close")
self.push_install = QPushButton(self)
self.push_install.setText("Install eventFilter")
self.push_deinstall = QPushButton(self)
self.push_deinstall.setText("Deinstall eventFilter")
layout = QVBoxLayout(centralwidget)
layout.addWidget(self.input_line_edit)
layout.addWidget(self.push_install)
layout.addWidget(self.push_deinstall)
layout.addWidget(self.close_push)
self.setCentralWidget(centralwidget)
return
def install_filter_event(self, widget_object):
widget_object.installEventFilter(self)
return
def deinstall_filter_event(self, widget_object):
widget_object.removeEventFilter(self)
return
def init_signal_slot_push_button(self):
self.close_push.clicked.connect(self.close)
self.push_install.clicked.connect(lambda: self.install_filter_event(self.input_line_edit))
self.push_deinstall.clicked.connect(lambda: self.deinstall_filter_event(self.input_line_edit))
return
def strip_string(self, content, site=None):
if site == "right":
return content.rstrip()
elif site == "right_left":
return content.strip()
elif site == "left":
return content.lstrip()
def eventFilter(self, received_object, event):
content_line_edit = unicode(received_object.text())
if event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Space:
'''
Yes, the user did press the Space-Key. We
count how often he pressed the space key.
'''
self.count_space_pressed = self.count_space_pressed + 1
if int(self.count_space_pressed) > 1:
'''
The user did press the space key more than 1 time.
'''
self.close_push.setEnabled(False)
'''
Now we know the user did press the
space key more than 1 time. We take a look,
if variablenamed (sel.current_pos) is None.
That means, no current position is saved.
'''
if self.current_pos is None:
'''
Well no current position is saved,
that why we save the new position anf
then we set the position of the cursor.
'''
self.current_pos = received_object.cursorPosition()
received_object.setCursorPosition(int(self.current_pos))
received_object.clear()
received_object.setText(self.strip_string(content_line_edit, site="right"))
else:
'''
Well the user press the space key again, for
example 3, 4, 5, 6 times we want to keep the
old position of the cursor until he press
no space key.
'''
received_object.setCursorPosition(int(self.current_pos))
'''
We have to remove all spaces in a string
on the right side and set the content on QLineEdit-widget.
'''
received_object.clear()
received_object.setText(self.strip_string(content_line_edit, site="right"))
else: pass
else:
'''
No the user didn't press the space key.
So we set all setting on default.
'''
self.close_push.setEnabled(True)
self.current_pos = None
self.count_space_pressed = 0
received_object.clear()
received_object.setText(self.strip_string(content_line_edit, site="left"))
# Call Base Class Method to Continue Normal Event Processing
return QMainWindow.eventFilter(self, received_object, event)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
EDIT:
import sys, re
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.edit = QtGui.QLineEdit(self)
self.edit.textChanged.connect(self.handleTextChanged)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
# First we save the the regular expression pattern
# in a variable named regex.
## This means: one whitespace character, followed by
## one or more whitespaces chatacters
regex = r"\s\s+"
# Now we comple the pattern.
# After then we save the compiled patter
# as result in a variable named compiled_re.
self.compiled_re = re.compile(regex)
def handleTextChanged(self, text):
# When the text of a widget-object is changed,
# we do something.
# Here I am really not sure.
# Do you want to look if the given text isn't empty?
## No, we want to search the string to see if it
## contains any runs of multiple spaces
if self.compiled_re.search(text):
# We know that given text is a QString-object.
# So we have to convert the given text
# into a python-string, because we want to work
# with them in python.
text = unicode(text)
# NOTICE: Do replacements before and after cursor pos
# We save the current and correct cursor position
# of a QLineEdit()-object in the variable named pos.
pos = self.edit.cursorPosition()
# Search and Replace: Here the sub()-method
# replaces all occurrences of the RE pattern
# in string with text.
# And then it returns modified string and saves
# it in the variables prefix and suffix.
# BUT I am not sure If I understand this: [:pos]
# and [pos:]. I will try to understnand.
# I think we are talking about slicing, right?
# And I think the slicing works like string[start:end]:
# So text[:pos] means, search and replace all whitesapce
# at the end of the text-string. And the same again, but
# text[pos:] means, search and replace all whitesapce
# at the start of the string-text.
## Right, but the wrong way round. text[:pos] means from
## the start of the string up to pos (the prefix); and
## text[pos:] means from pos up to the end of the string
## (the suffix)
prefix = self.compiled_re.sub(' ', text[:pos])
suffix = self.compiled_re.sub(' ', text[pos:])
# NOTICE: Cursor might be between spaces
# Now we take a look if the variable prefix ends
# with a whitespace and we check if suffix starts
# with a whitespace.
# BUT, why we do that?
## Imagine that the string is "A |B C" (with the cursor
## shown as "|"). If "B" is deleted, we will get "A | C"
## with the cursor left between multiple spaces. But
## when the string is split into prefix and suffix,
## each part will contain only *one* space, so the
## regexp won't replace them.
if prefix.endswith(' ') and suffix.startswith(' '):
# Yes its True, so we overwrite the variable named
# suffix and slice it. suffix[1:] means, we starts
# at 1 until open end.
## This removes the extra space at the start of the
## suffix that was missed by the regexp (see above)
suffix = suffix[1:]
# Now we have to set the text of the QLineEdit()-object,
# so we put the both varialbes named prefix and suffix
# together.
self.edit.setText(prefix + suffix)
# After then, we have to set the cursor position.
# I know that the len()-method returns the length of the
# variable named prefix.
# BUT why we have to do that?
## When the text is set, it will clear the cursor. The
## prefix and suffix gives the text before and after the
## old cursor position. Removing spaces may have shifted
## the old position, so the new postion is calculated
## from the length of the current prefix
self.edit.setCursorPosition(len(prefix))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 150, 300, 100)
window.show()
sys.exit(app.exec_())
EDIT 2:
Two question:
First Question: in the if.condition, where we take a look if prefix ends and suffix starts with sapces, there we are about to remove the extra space at the start of the suffix. But why don't we also remove the extra space at start of the prefix?
Imagine: The user types " Prefix and Suffix " - with extra whitespaces at start and end. Don't we have to remove the extra space at start of the prefix - like:
prefix= prefix[:1]?
Second Question: At the end of the handleTextChanged()-method, we have to calculate the new position of the cursor. In the current case we use prefix to get the length of the string. Why not the len from the new modified text, that is a part from prefix and suffix?
Example: The old string is " Prefix and Suffix ", the user removes the word 'and". Now our string looks like " Prefix | Suffix ". After all whitespaces are removed we get the new modified text: "Prefix Suffix". Why don't we calculate the new position from the modified text? Or did I miss something?
EDIT 3:
I am sorry, I still don't understand the situation.
First situation: When the user types the following string: "A B C |" (| it is shown as cursor). Now the user presses the space key more than 2 times, we get a prefix that contains "A B C |" - and no suffix. And currently the length of the prexis is 6 - suffix has no lenght, because its empty. And the whole word is length 6. The current position of the cursor is 7.
Second situation: The user types "A B D E F |". And now he is realizing that a letter is missing: C. He moves his cursor back between B and D and types C and then he is about to press the space key 2 times. Now we have prefix that contains "A B C " and suffix which content "D E F". The length of prefix is 6 and of suffix is 5. The length of the whole word is 11. And in this moment the current position of the cursor is 7. In this situation you take the length of prefix and set the cursor position, right?
Filtering key-presses is not enough if you really want to prevent multiple spaces.
For instance, the user can simply drag and drop multiple spaces; or paste them either with the mouse, the built-in context menu, or with the standard keyboard shortcuts.
It's also very easy to break your space-key counting method: for example, just type A B C then move back two places and delete B!
A much more robust way to do this is to connect to the textChanged signal and use a regexp to check if there's any multiple spaces. If there are, use the same regexp to replace them, and then restore the cursor to it's original position.
Here's a demo:
import sys, re
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.edit = QtGui.QLineEdit(self)
self.edit.textChanged.connect(self.handleTextChanged)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
self.regexp = re.compile(r'\s\s+')
def handleTextChanged(self, text):
if self.regexp.search(text):
text = unicode(text)
# do replacements before and after cursor pos
pos = self.edit.cursorPosition()
prefix = self.regexp.sub(' ', text[:pos])
suffix = self.regexp.sub(' ', text[pos:])
# cursor might be between spaces
if prefix.endswith(' ') and suffix.startswith(' '):
suffix = suffix[1:]
self.edit.setText(prefix + suffix)
self.edit.setCursorPosition(len(prefix))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 150, 300, 100)
window.show()
sys.exit(app.exec_())
if you are using python and you have created button for removing the last character, do the following
self.PB_Back.clicked.connect(self.Keypad_Back)
def Keypad_Back(self):
self.LE_Edit.setText(self.LE_Edit.text()[:-1])
this will remove last character one at a time
to delete all the character at once, do the following
self.PB_DeleteResult.clicked.connect(self.Keypad_DeleteResult)
def Keypad_DeleteResult(self):
self.LE_Edit.setText("")

Python 2.7 - Tkinter - Button to move to next item in listbox

def nextItem(self):
active = self.skill_list_listbox.get(tk.ACTIVE)
listbox_contents = self.skill_list_listbox.get(0, tk.END)
current_pos = listbox_contents.index(active)
if current_pos + 1 < len(listbox_contents):
new_pos = current_pos + 1
self.skill_list_listbox.activate(new_pos)
self.skill_list_listbox.selection_set(tk.ACTIVE)
From what I can see within documentation this should highlight and activate the next item in the listbox. If I omit the selection_set I get what I'm looking for but there's no indicator of what's active. Adding it highlights an item, but if you continue to click the "next" button it simply adds to the highlight instead of just highlighting one item creating a long section of highlighted items, which I don't want. I've tried several different methods and this has got me the closest. If there was a 'clear selection' method I suppose I could get my desired effect of just having the next item selected and highlighted, but 3 calls just to do that seems a bit much for a common task? Any thoughts, or suggestions?
Below is an example of what I think you are trying to accomplish, using a button to select the next item in a Listbox. The gist of it is in the button's callback function, which calls selection_clear then selection_set.
Updated the example, hopefully a bit clearer as to what it happening
import Tkinter
class Application(Tkinter.Frame):
def __init__(self, master):
Tkinter.Frame.__init__(self, master)
self.master.minsize(width=256, height=256)
self.master.config()
self.pack()
self.main_frame = Tkinter.Frame()
self.some_list = [
'One',
'Two',
'Three',
'Four'
]
self.some_listbox = Tkinter.Listbox(self.main_frame)
self.some_listbox.pack(fill='both', expand=True)
self.main_frame.pack(fill='both', expand=True)
# insert our items into the list box
for i, item in enumerate(self.some_list):
self.some_listbox.insert(i, item)
# add a button to select the next item
self.some_button = Tkinter.Button(
self.main_frame, text="Next", command=self.next_selection)
self.some_button.pack(side='top')
# not really necessary, just make things look nice and centered
self.main_frame.place(in_=self.master, anchor='c', relx=.5, rely=.5)
def next_selection(self):
selection_indices = self.some_listbox.curselection()
# default next selection is the beginning
next_selection = 0
# make sure at least one item is selected
if len(selection_indices) > 0:
# Get the last selection, remember they are strings for some reason
# so convert to int
last_selection = int(selection_indices[-1])
# clear current selections
self.some_listbox.selection_clear(selection_indices)
# Make sure we're not at the last item
if last_selection < self.some_listbox.size() - 1:
next_selection = last_selection + 1
self.some_listbox.activate(next_selection)
self.some_listbox.selection_set(next_selection)
root = Tkinter.Tk()
app = Application(root)
app.mainloop()