Question

I've created a couple of listbox widgets where selecting some items in one and then pressing a button below moves the items to the other.

This worked absolutely fine - but I wanted to reuse the container frame because the layouts of the 2 frames was identical (apart from the heading label, and the functions when the buttons were pressed). So I moved all the code except the button functions to a class "ColumnSelector".

However to move data from one "ColumnSelector" to the other I need references to the listboxes inside the instances. Below is the structure of what I would like to do, but I am not sure if this is possible.

I have tried some other ways such as creating the listbox outside of the ColumnSelector class and passing it through, but I ran into problems doing it that way too.

What would be the best way to reference widgets inside instances of other classes?

    # Data to be included in second listbox widget
    startingSelection = ('Argentina', 'Australia', 'Belgium', 'Brazil', 'Canada', 'China', 'Denmark')

    # Two functions performed by the ColumnSelectors
    def removeSelected(*args):
        idxs = selectedColumns.listBox.curselection() # <- Does not reference correctly
        if len(idxs)>=1:
            for n in reversed(range(len(idxs))):
                idx = int(idxs[n])
                item = selectedColumns.listBox.get(idx)
                selectedColumns.listBox.delete(idx)
                availableColumns.listBox.insert(availableColumns.listBox.size(), item)

    def addSelected(*args):
        idxs = availableColumns.listBox.curselection() #<- Does not reference correctly
        if len(idxs)>=1:
            for n in reversed(range(len(idxs))):
                idx = int(idxs[n])
                item = availableColumns.listBox.get(idx)
                availableColumns.listBox.delete(idx)
                selectedColumns.listBox.insert(selectedColumns.listBox.size(), item)

    # Create ColumnSelectors, pass heading title and function to perform
    selectedColumns = ColumnSelector(self, "Columns to include in export", (), removeSelected).grid(column=0, row=0, sticky=(N,W))
    availableColumns = ColumnSelector(self, "Available Columns", startingSelection, addSelected).grid(column=1, row=0, sticky=(N,W))

class ColumnSelector(ttk.Frame):
    def __init__(self, parent, labelText, startingSelection, function ):
        listBox = Listbox(self, height=5, selectmode='multiple')
        removeColumnsButton = ttk.Button(self, text="Move", command=function)
        #(etc...)
Was it helpful?

Solution

What would be the best way to reference widgets inside instances of other classes?

I think the most common-use case for this is reusing an object an indefinite number of times, as it looks like you're trying to do with some listboxes that are set up inside a frame. In this case, I think you should put as much repeatable code as possible in the subclass and create a method in it that returns just what you want. When you create an instance of the subclass inside your main class, then you can access its methods when you need to (ie, selectedColumns.get_all_listbox_values()).

One thing you should keep in mind is that the instance won't work right if you create it and grid it on the same line:

No

selectedColumns = ColumnSelector(self, "Columns to include in export", (), removeSelected).grid(column=0, row=0, sticky=(N,W))
selectedColumns.get_all_listbox_values()
>>> AttributeError: 'NoneType' object has no attribute 'get_all_listbox_values'

Yes

selectedColumns = ColumnSelector(self, "Columns to include in export", (), removeSelected)
selectedColumns.grid(column=0, row=0, sticky=(N,W))
selectedColumns.get_all_listbox_values()
>>> (0, 1, 2, etc)

Below is an example of one way to set up your script. There's a main class (App) and another class that inherits from Frame (MyEntry) that can be used in App multiple times. There's one button in the App class that prints out results from a method in MyEntry that calculates a couple of values. Hopefully it helps to give you some ideas about structuring your code.

class App(Frame):
    '''the main window class'''
    def __init__(self, parent):
        Frame.__init__(self, parent)

        # create instances of MyEntry, passing whatever operators as args
        # we can make as many of these instances as we need in just a couple of lines
        # and it retains readability
        self.divide = MyEntry(self, '/')
        self.multiply = MyEntry(self, '*')

        self.divide.pack()
        self.multiply.pack()

        Button(self, text='Calculate', command=self._print_result).pack()

    def _print_result(self):
        '''print the return of the calculate method from the instances of
        the MyEntry class'''
        print self.divide.calculate()
        print self.multiply.calculate()

class MyEntry(Frame):
    '''creates two entries and a label and has a method to calculate
    the entries based on an operator'''
    def __init__(self, parent, operator): # include the operator as an arg
        Frame.__init__(self, parent)

        # make an instance variable from the operator to access it between methods
        self.operator = operator

        # make two entries
        self.num1 = Entry(self)
        self.num2 = Entry(self)

        # grid the entries and a label which contains the operator
        self.num1.grid(row=0, column=0)
        Label(self, text=self.operator).grid(row=0, column=1)
        self.num2.grid(row=0, column=2)

    def calculate(self):
        '''return the value of the two entries based on the operator specified'''
        if self.operator is '/':
            return int(self.num1.get()) / int(self.num2.get())
        elif self.operator is '*':
            return int(self.num1.get()) * int(self.num2.get())
        else:
            return

root = Tk()
App(root).pack()
mainloop()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top