Removing selected items from the listbox and from

2019-09-14 18:40发布

问题:

I have an application written in C# that needs to be converted to Python, since I have recently switched to Linux. It's a simple GUI application to manage unknown words while learning a new language. Nevertheless, I need remove_item() function for which I also need find_word() function.

In C#, I would create two following methods:

void Remove()
{
    Word word = new Word();
    try { word = FindWord(listView1.SelectedItems[0].Text); }
    catch { return; }
    if (listView1.SelectedItems.Count > 0)
    {
        try
        {
            foreach (ListViewItem eachItem in listView1.SelectedItems)
            {
                words.RemoveAll(x => x.WordOrPhrase == eachItem.Text);
                listView1.Items[listView1.Items.Count - 1].Selected = true;
                listView1.Items.Remove(eachItem);
            }
        }
        catch { }
        ClearAll();
        ReadOnlyON();
    }
    else
    {
        MessageBox.Show("You have not selected any words!", "Notification", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
    ReadOnlyOFF();
    WordCount();
    Sync();
}

private Word FindWord(string word)
{
    return words.Find(x => x.WordOrPhrase == word);
}

...but I'm still a newbie when it comes to Python, so any help would be appreciated. Here is what I have so far:

When it comes to the FindWord() method, it could be rewritten as following:

def FindWord(word):
    for x in words:
        if x.WordOrPhrase == word:
            return x

or

def FindWord(word):
    return next((x for x in words if x.WordOrPhrase == word), None)

or

def FindWord(word):
    return next(filter(lambda x: x.WordOrPhrase == word, words), None)

...but I'm struggling to rewrite Remove() method. Here is one way:

def remove_item(self):
    word = self.listBox.get(ACTIVE)
    new_word_list = []  # initialize empty list
    delete_idxs = []
    for idx, item in enumerate(self.words):
        if item.wordorphrase == word:
            delete_idxs.append(idx)
        else:
            new_word_list.append(item)
    self.words = new_word_list  # overwrite the old word_list with the new one
    for idx in reversed(delete_idxs):
        self.listBox.delete(idx)

...what I would like most is converting my C# method to Python. Here is what I have so far:

def remove_item(self):

    word = Word()

    try:
        word = find_word(self.listBox.curselection())
    except:
        return

    if self.listBox.len(curselection()) > 0:
        try:
            for item in self.listBox.curselection():
                self.words.remove(lambda x: x.wordorphrase == item.text)
                # listView1.Items[listView1.Items.Count - 1].Selected = true;
                self.listBox.remove(item)
        except:
            pass
        self.clear_all()

    else:
        pass
        # show messagebox

I don't know how to access:

  • listView1.SelectedItems[0].Text
  • listView1.SelectedItems.Count > 0
  • listView1.SelectedItems
  • listView1.Items[listView1.Items.Count - 1].Selected

Here is what I have done so far:

# Vocabulary.py
# GUI program to manage unknown words

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import xml.etree.ElementTree as ET
import os


class Word:

    def __init__(self, wordorphrase, explanation, translation, example):
        self.wordorphrase = wordorphrase
        self.explanation = explanation
        self.example = example
        self.translation = translation

class Vocabulary(Frame):

    def __init__(self, master):
        Frame.__init__(self, master)
        self.master = master
        self.master.resizable(width = False, height = False)
        self.master.title("Vocabulary")
        self.create_widgets()
        self.words = []
        self.load_words()

    def on_closing(self):

        self.save_all()

        if messagebox.askokcancel("Quit", "Do you want to quit?"):
            self.master.destroy()

    def create_widgets(self):

        self.buttons_frame = Frame(self.master)
        self.buttons_frame.grid(row = 10, sticky = W)

        self.search_frame = Frame(self.master)
        self.search_frame.grid(row = 1, sticky = W, columnspan = 2)

        self.comboBox = ttk.Combobox(self.search_frame,
                                     width = 3)
        self.comboBox.grid(row = 0, column = 14, sticky = W)
        self.comboBox['values'] = ( 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' )

        self.btn_Add = Button(self.buttons_frame,
                              text = 'Add',
                              command = self.add_item)
        self.btn_Add.grid(row = 0, sticky = W)

        self.btn_Remove = Button(self.buttons_frame,
                                 text = 'Remove',
                                 command = self.remove_item)

        self.btn_Remove.grid(row = 0, column = 1, sticky = W)

        self.btn_Edit = Button(self.buttons_frame,
                               text = 'Edit',
                               command = self.edit_item)
        self.btn_Edit.grid(row = 0, column = 2, sticky = W)

        self.btn_Save = Button(self.buttons_frame,
                               text = 'Save',
                               command = self.save_item)
        self.btn_Save.grid(row = 0, column = 3, sticky = W)

        self.btn_Refresh = Button(self.buttons_frame,
                                  text = 'Refresh',
                                  command = self.refresh_all)
        self.btn_Refresh.grid(row = 0, column = 4, sticky = W)

        self.lblSearch = Label(self.search_frame, text = 'SEARCH: ')
        self.lblSearch.grid(row = 0, column = 5, sticky = W)

        self.txt_Search = Text(self.search_frame,
                               height = 1,
                               width = 70)
        self.txt_Search.grid(row = 0, column = 6, columnspan = 3, sticky = W)

        self.lblWordsOrPhrases = Label(self.master, text = 'WORDS/PHRASES:')
        self.lblWordsOrPhrases.grid(row = 2, column = 0)

        self.lblWordOrPhrase = Label(self.master, text = 'Word or phrase:')
        self.lblWordOrPhrase.grid(row = 2, column = 1, sticky = W)

        self.listBox = Listbox(self.master,
                               selectmode='extended',
                               height = 34,
                               width = 38)
        self.listBox.grid(row = 3, column = 0, rowspan = 7, sticky = W)

        self.txt_WordOrPhrase = Text(self.master,
                                     height = 1,
                                     width = 40)
        self.txt_WordOrPhrase.grid(row = 3, column = 1, sticky = N)

        self.lblExplanation = Label(self.master, text = 'Explanation:')
        self.lblExplanation.grid(row = 4, column = 1, sticky = W)

        self.txt_Explanation = Text(self.master,
                                    height = 10,
                                    width = 40)
        self.txt_Explanation.grid(row = 5, column = 1, sticky = N)

        self.lblTranslation = Label(self.master, text = 'Translation:')
        self.lblTranslation.grid(row = 6, column = 1, sticky = W)

        self.txt_Translation = Text(self.master,
                                    height = 10,
                                    width = 40)
        self.txt_Translation.grid(row = 7, column = 1, sticky = N)

        self.lblExamples = Label(self.master, text = 'Example(s):')
        self.lblExamples.grid(row = 8, column = 1, sticky = W)

        self.txt_Example = Text(self.master,
                                height = 10,
                                width = 40)
        self.txt_Example.grid(row = 9, column = 1, sticky = S)

    def load_words(self):

        self.listBox.delete(0, END)
        self.words.clear()

        path = os.path.expanduser('~/Desktop')
        vocabulary = os.path.join(path, 'Vocabulary', 'Words.xml')

        if not os.path.exists(vocabulary):
            if not os.path.exists(os.path.dirname(vocabulary)):
                os.mkdir(os.path.dirname(vocabulary))
            doc = ET.Element('Words')
            tree = ET.ElementTree(doc)
            tree.write(vocabulary)
        else:
            tree = ET.ElementTree(file=vocabulary)

        for node in tree.findall('WordOrPhrase'):
            w = Word(node.find('Word').text, node.find('Explanation').text, node.find('Translation').text,
                     node.find('Examples').text)

            self.words.append(w)
            self.listBox.insert(END, w.wordorphrase)

    def save_all(self):

        path = os.path.expanduser('~/Desktop')
        vocabulary = os.path.join(path, 'Vocabulary', 'Words.xml')

        tree = ET.ElementTree(file=vocabulary)

        for xNode in tree.getroot().findall('WordOrPhrase'):
            tree.getroot().remove(xNode)

        for w in self.words:
            xTop = ET.Element('WordOrPhrase')
            xWord = ET.Element('Word')
            xExplanation = ET.Element('Explanation')
            xTranslation = ET.Element('Translation')
            xExamples = ET.Element('Examples')

            xWord.text = w.wordorphrase
            xExplanation.text = w.explanation
            xTranslation.text = w.translation
            xExamples.text = w.example

            xTop.append(xWord)
            xTop.append(xExplanation)
            xTop.append(xTranslation)
            xTop.append(xExamples)

            tree.getroot().append(xTop)

        tree.write(vocabulary)

    def add_item(self):

        w = Word(self.get_word(), self.get_explanation(), self.get_translation(), self.get_example())

        self.words.append(w)

        self.listBox.insert(END, w.wordorphrase)

        self.clear_all()

        self.save_all()

    def remove_item(self):

        word = Word()

        try:
            word = find_word(self.listBox.curselection())
        except:
            return

        if self.listBox.len(curselection()) > 0:
            try:
                for item in self.listBox.curselection():
                    self.words.remove(lambda x: x.wordorphrase == item.text)
                    # listView1.Items[listView1.Items.Count - 1].Selected = true;
                    self.listBox.remove(item)
            except:
                pass
            self.clear_all()

        else:
            pass
            # show messagebox

    def edit_item(self):
        pass

    def save_item(self):
        pass

    def clear_all(self):
        self.txt_WordOrPhrase.delete('1.0', END)
        self.txt_Explanation.delete('1.0', END)
        self.txt_Translation.delete('1.0', END)
        self.txt_Example.delete('1.0', END)

    def refresh_all(self):
        pass

    def get_word(self):
        return self.txt_WordOrPhrase.get('1.0', '1.0 lineend')

    def get_explanation(self):
        return self.txt_Explanation.get('1.0', '1.0 lineend')

    def get_translation(self):
        return self.txt_Translation.get('1.0', '1.0 lineend')

    def get_example(self):
        return self.txt_Example.get('1.0', '1.0 lineend')

    def find_word(word):
        for x in self.words:
            if x.wordorphrase == word:
                return x


def main():
    root = Tk()
    gui = Vocabulary(root)
    root.protocol('WM_DELETE_WINDOW', gui.on_closing)
    root.mainloop()

if __name__ == '__main__':
    main()

回答1:

For my listboxes, I generally have the mode set to EXTENDED so that users can select one or more items and remove all at once. I do so via the following method:

# Function with Tk.Listbox passed as arg
def remove_from(list_box):

    # Tuple of currently selected items in arg
    selected_items = list_box.curselection()

    # Initialize a 'repositioning' variable
    pos = 0

    for item in selected_items:
        # Set index of each item selected
        idx = int(item) - pos
        # Deletes only that index in the listbox
        list_box.delete(idx, idx)
        # Increments to account for shifts
        pos += 1

For example, lets say in my listbox I had 4 items. I then select the first item and the third item. By calling list_box.curselection() I've received the following:

selected_items = (0, 2)

Where 0 is the first item's position in the listbox, and 2 is the third item's position. Then for each item in my tuple, I establish its index.

Walking through this, for the first item the following takes place:

idx = 0 - 0
list_box.delete(0, 0)
pos = 1

So now I have deleted the item at position 0 (e.g. the first item) and my listbox has shifted! So the second is now the first, third is the second and fourth is the third. However, my tuple has not changed as it was the positions in the listbox of the original selection. This is key. What happens next is:

idx = 2 - 1
list_box.delete(1, 1)
pos = 2

Since the listbox shifted, position 1 now corresponds to the item that was originally in the third position of the listbox. This can continue for n positions.

For self.words removal

You could try the following:

# Function with Tk.Listbox and self.words[] passed as args
def remove_from(list_box, list):

    # Tuple of currently selected items in arg
    selected_items = list_box.curselection()

    # List of Words already constructed
    word_list = list

    # Initialize a 'repositioning' variable
    pos = 0

    for item in selected_items:
        # Set index of each item selected
        idx = int(item) - pos
        # Deletes only that index in the listbox
        list_box.delete(idx, idx)
        # Gets the string value of the given index
        word = list_box.get(idx, idx)
        # Removes the word from the list
        if any(word in x for x in word_list):
            word_list.remove(word)
        # Increments to account for shifts
        pos += 1