Seite 1 von 1

Autovervollständigung von Entry-Widgets

Verfasst: Dienstag 20. Mai 2008, 01:29
von ayJay
Hallo zusammen,

ich bin grade dabei, ein kleines Tool zusammenzufrickeln (wenns dann mal läuft wird der Code noch aufgehübscht, aber im Moment bin ich noch sehr mit Trial-and-Error beschäftigt).

zu meinen Vorkenntnissen: ich kann gut (bis sehr gut) C, und seit einigen Wochen befasse ich mich auch mit objektorientierter Programmierung (gezwungenermaßen C#, aber es wird auf C++ hinauslaufen). Python ROCKT einfach mal, wie schnell man damit lauffähige Programme hinbekommt ist der Wahnsinn.

Nun zu meinem Problem: ich habe eine Liste von Strings (Städtenamen) und ein Entry-Widget in meinem Tkinter-Programm. Ich möchte, dass diese nun bei der Eingabe automatisch vorgeschlagen werden. Wenn man z.B. ein [A] tippt, wird "Aachen" vorgeschlagen, wenn ein [S] folgt "Aschaffenburg" usw. Dazu müsste ich ja so etwas wie ein "keypressed-event" von dem Entry-Widget abfragen können.
Geht sowas? Das wäre nämlich die optimale Lösung. Alternativ habe ich es auch schon einmal mit einer Art Combobox versucht, aber die war leider nicht mit der Tastatur durchsuchbar.

Weiß jemand Rat?

Verfasst: Dienstag 20. Mai 2008, 06:36
von wuf
Hallo ayJay

Kannst du das einmal ausprobieren:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import Tkinter as tk

def entry_keypress(event):
	""" Ausgabe der aktivierten Taste """
	print 'Key',event.keysym
	
root = tk.Tk()
root.geometry('200x100')

entry = tk.Entry(root,
	width = 20,
	 bg   = 'white',
	 bd   = 1,
	 highlightthickness = 0
	 )
entry.place(x=20,y=20)
entry.bind('<KeyPress>',entry_keypress)
	
entry.focus_set()
	
root.mainloop()
Gruss wuf :wink:

Verfasst: Dienstag 20. Mai 2008, 12:22
von ayJay
*thumbsup*

[edith]Hochmut kommt vor dem Fall[/edith]

Code: Alles auswählen

def autocomplete(namepart, obj):
	for ort in orte:
		if ort.upper().startswith(namepart.upper()):
			obj.delete(0,END)
			obj.insert(0, ort)
			break
Mein Problem nun: das eingegebene Zeichen wird hinten an den automatisch eingefügten Ort drangehängt. Wie es scheint, wird die mit bind angegebene Funktion aufgerufen, bevor das Zeichen eingetragen wird.

Wie kann ich das überflüssige Zeichen loswerden?

Verfasst: Dienstag 3. Juni 2008, 12:51
von ayJay
*push*

Ich habe im Moment:

Code: Alles auswählen

	if len(event.keysym)==1:
		start.insert(INSERT, event.keysym)
		autocomplete(start.get(),startAutocomplete)
		start.selection_range(start.index(INSERT)-1,start.index(INSERT))
Dieser Handler macht aber Probleme, wenn z.B. in dem Entry-Widget was markiert war. Ich könnte natürlich alles per Hand handlen, aber im Grunde würde es reichen, wenn der Handler erst nach der Aktualisierung des Textes aufgerufen wird.

Verfasst: Mittwoch 4. Juni 2008, 00:16
von wuf
Hallo ayJay

Hier ist eine von vielen Lösungen für dein Problem. Es ist alles optimierbar und erweiterbar. Hoffe dich richtig verstanden zu haben. Eine Text-Ergänzung muss für mich aus einer Auswahl-Listbox erfolgen. Es gibt näturlich noch Lösungen die auf Text-Rechtschreibung basieren. Es gibt sicher noch Forum-Freunde mit besseren Ideen.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# Erstellt : wuf 03.06.2008
# Geändert : wuf 03.06.2008

# Funktion:  Bei einem Entry-Widget wird während
#            der Eingabe der eingegebene Text laufend
#            mit einer Wortliste verglichen. Befinden
#            sich übereinstimmende Wörter in der Wort-
#            liste werden diese in einer Auswahl-Listbox
#            angezeigt. Besteht keine Übereinstimmung,
#            oder wird die Eingabe mit der Taste 'Enter'
#            beendet, bewirkt dies eine Schliessung der
#            Auswahl-Listbox.
#            Eine Wort-Selektion in der Auswahl-Listbox
#            mit der linken Maustaste bewirkt ein Über-
#            trag des selektierten Wortes in das Entry-
#            Widget. Mit dem Übertrag wird die Auswahl-
#            Listbox geschlossen.
#
# Pendenzen: a) Die Auswahl-Listbox muss beim Einsatz
#            einer langen Wortliste und breiteren
#            Wörtern noch mit Scrollbars ergänzt werden.
#
#            b) Die Auswahl-Listbox sollte bei einer
#            realen Anwendung in einem Popup-Fenster
#            (Toplevel-Fenster) eingebettet sein, damit
#            unterhalb des Entry-Widgets weitere Widgets
#            platziert werden können. Somit können diese
#            beim sichbar werden der Auswahl-Listbox
#            problemlos zugedeckt werden

# Status:  Prototype

import Tkinter as tk

def entry_keypress(event):
    """ Ausgabe der aktivierten Taste """

    global listbox_show_flag,listbox #,listbox_place_info

    #~~ Taste
    key = event.keysym
#    print 'Debug:Key',key

    if key == 'Return':
        #~~ Es wurde die 'Enter-Taste aktiviert
        #   Mache die Auswahl-Listbox unsichtbar
        show_listbox(tk.FALSE)
        entry_value = entry.get()
        print 'Debug:Entry-Value',entry_value

        return entry_value

    show_listbox(tk.TRUE)

    #~~ Lade den Eingabetext
    entry_text = event.widget.get()
#    print 'Debug:Entry-Text',entry_text,len(entry_text)

    update_complete_box(entry_text)

def update_complete_box(entry_text):
    """Aktualisiere Auswahl-Listbox"""

    global listbox_show_flag,listbox #,listbox_place_info

    #~~ Länge des Eingabtextes
    len_entry_text = len(entry_text)

    #~~ Durchsuche die City-Wortliste
    choice_list = []
    for index,city in enumerate(city_list):
        #   Kontrolle: Übersteigt die Anzahl Zeichen des Eingabetextes
        #   die Anzahl Zeichen der City-Bezeichnung
        if len_entry_text <= len(city):
            #~~ Nein
            #   Trenne die Anzahl Zeichen vom City-Wort ab. Die
            #   Anzahl abzutrennenden Zeichen entspricht der
            #   Länge des Eingabetextes
            city_fraction = city[0:len_entry_text]
#            print 'Dibug:Fraktion',city_fraction
            #~~ Vergleiche den vom City-Wort abgetrennten Text
            #   mit der Texteingabe
            if city_fraction == entry_text:
                #~~ Es gibt eine Übereinstimmung zwischen
                #   der Texteingabe und dem City-Wort aus
                #   der City-Liste
                print 'Debug:City',city_fraction
                #~~ Füge das gefundene City-Wort der
                #   Auswahllist hinzu
                choice_list.append(city_list[index])

    #~~ Lösche den Inhalt der Auswahl-Listbox
    listbox.delete(0,'end')

    #~~ Fülle die Auswahl-Listbox mit den auswählbaren
    #   City-Wörtern. Skaliere die Höhe der Auswahl-Listbox
    num_cities = len(choice_list)
#    print 'Debug:Anzahl-Cities',num_cities
    if not num_cities:
        #~~ Die Auswahlliste enthält keine Einträge
        show_listbox(tk.FALSE)
        return

    #~~ Justiere die Höhe der Auswahl-Listbox
    listbox.configure(height=num_cities)
    for city in choice_list:
        #~~ Lade das Auswahlwort in die Auswahl-Listbox
        listbox.insert(0,city)

def show_listbox(show_flag):
    """Steuert die Sichtbarkeit der Auswahl-Listbox"""

    global listbox_show_flag,listbox

    if show_flag:
        #~~ Mache die Auswahl-Listbox sichtbar
        if not listbox_show_flag:
            listbox_show_flag = show_flag
            listbox.place_configure(listbox_place_info)
    else:
        #~~ Mache die Auswahl-Listbox unsichtbar
        if listbox_show_flag:
            listbox_show_flag = show_flag
            listbox.place_forget()

def choice(event):
    """Wahl aus der Auswahl-Listbox"""

    global listbox,entry

    #~~ Listbox: Lade den index der Auswahl
    list_index = listbox.curselection()[0]
    #~~ Listbox: Lade den selektierten Namen
    selection  = listbox.get(list_index)
    #~~ Entry: Lösche den Eingabetext
    entry.delete(0)
    #~~ Entry: Übertrage den aus der Auswahl-Listbox
    #   selektierten Namen in das Entryfeld
    entry.insert(0,selection)
    #~~ Mache die Auswahl-Listbox unsichtbar
    show_listbox(tk.FALSE)

#    print 'Debug:Listbox-Selektion',selection

#**** Start des Hauptprogrammes ****
root = tk.Tk()
root.geometry('220x150')
root['bg'] = 'steelblue'
root.title('wuf:choice')

#~~ Ortschaften-Liste
city_list = ['Basel','Bern','Burgdorf','Altdorf','Luzern','Genf','Zürich']

#~~ Listbox Sichbar-Flag
listbox_show_flag = tk.FALSE

#~~ Erstelle ein Entry-Widget
entry = tk.Entry(root,
    width = 28,
    bg    = 'white',
    bd    = 1,
    font  = ('helvetica','8'),
    highlightthickness = 0
    )
entry.place(x=20,y=20)
entry.bind('<KeyRelease>',entry_keypress)

#~~ Erstelle ein Listbox-Widget
listbox = tk.Listbox(root,
    width = 28,
    bg    = 'lightblue',
    fg    = 'blue',
    bd    = 0,
    font  = ('helvetica','8'),
    selectmode = 'single',
    highlightthickness = 0
    )
listbox.place(x=21,y=40)
listbox.bind('<ButtonRelease>',choice)

#~~ Mache die Auswahl-Listbox unsichtbar
listbox_place_info = listbox.place_info()
listbox.place_forget()

#~~ Setze den Fokus auf das Eingab-Widget
entry.focus_set()

root.mainloop()
Viel Spass beim Programmieren :lol:

Gruss wuf :wink:

Verfasst: Mittwoch 4. Juni 2008, 08:02
von ayJay
Vielen Dank für deine Mühe!

Wenn ich den Code richtig überblicke reicht es, wenn ich bei mir <KeyPress> mit <KeyRelease> ersetze, weil dann das widget beim event schon den aktualisierten Inhalt hat. Das Vervollständigen soll dann vollständig in dem Entry-widget erfolgen, ohne dass man zur Maus greifen muss.