Skip to content Skip to sidebar Skip to footer

Binding Two Comboboxes To One Function

I'm having trouble with binding to functions. Using Dijkstra's algorithm to find the shortest path, I want it so when both comboxboxes choose a letter, it will print out the result

Solution 1:

(Revised to reflect the not insignificant changes you made to the code in your question and to be much more object-oriented.)

Actually, the code currently in your question raises a NameError: name 'x' is not defined on the cost, path = shortest_path(x, y) statement, and only after fixing that would it raise something like the shortestPath() takes exactly 2 arguments (1 given) you mention.

The problem is you need to define a proper Tkinter event handler function, which are defined to normally only receive one argument (in addition to self if it happens to be a class method), namely the event which triggered them.

So in this case that's the cause of the error: shortestPath() expects an x and y value to be passed to it but it's only receiving one — the "<<ComboboxSelected>>" event from Tkinter.

Here's what I mean:

from Tkinter import *
import ttk
import heapq

# Program GUIclassApp(object):
    def__init__(self):
        self.root = Tk()
        self.graph = {
            'a': {'w': 16, 'x': 9, 'y': 11},
            'b': {'w': 11, 'z': 8},
            'w': {'a': 16, 'b': 11, 'y': 4},
            'x': {'a': 9, 'y': 12, 'z': 17},
            'y': {'a': 11, 'w': 4, 'x': 12, 'z': 13},
            'z': {'b': 8, 'x': 17, 'y': 13},
        }
        self.termini = {}  # storage for node ids of each terminus
        self.create_gui_widgets()
        self.root.mainloop()

    defcreate_gui_widgets(self):
        values = tuple(self.graph.keys())  # valid node ids
        self.combo1 = self.create_combobox('start', values, 5, 75, 5, 20)
        self.combo2 = self.create_combobox('end', values, 5, 100, 5, 20)

    defcreate_combobox(self, terminus, values, x, y, height, width):
        " Utility to create ComboBox of node ids for a terminus of route. "
        combobox = ttk.Combobox(self.root, height=height, width=width)
        combobox['values'] = values
        combobox.terminus = terminus
        combobox.current(0)
        self.termini[combobox.terminus] = combobox.get()  # set to current value
        combobox.place(x=x, y=y)
        combobox.bind("<<ComboboxSelected>>", self.combobox_event_handler)
        return combobox

    defcombobox_event_handler(self, event):
        " Event handler for both ComboBoxes. "
        combobox = event.widget  # ComboBox triggering event
        self.termini[combobox.terminus] = combobox.get()  # set selected node id# if both a start and end terminus are defined and different, then# find and display shortest path between them
        start, end = self.termini.get('start'), self.termini.get('end')
        if start and end and start != end:
            cost, path = self.shortest_path(start, end)
            print('cost: {}, path: {}'.format(cost, path))

    # Dijkstra's search algorithmdefshortest_path(self, start, end):
        graph = self.graph  # copy to local var for faster access
        queue, seen = [(0, start, [])], set()
        whileTrue:
            cost, v, path = heapq.heappop(queue)
            if v notin seen:
                path = path + [v]
                seen.add(v)
                if v == end:
                    return cost, path
                fornext, c in graph[v].iteritems():
                     heapq.heappush(queue, (cost + c, next, path))

App()

Solution 2:

The error states that your function needs two arguments, which is true. The reason one is getting passed in is simply because that's how bindings work. In tkinter, when you create a binding and the binding fires, the function always gets an argument which is an object that contains information about the event.

The solution is simple: write a new function for your comboboxes that accepts the event, gathers the information it needs, and then does the calculation. You don't need to actually use the event object, but you need to accept it.

Your choice of using an object-oriented architecture makes this very easy to implement:

self.combo1.bind("<<ComboboxSelected>>",self.on_combobox)
self.combo2.bind("<<ComboboxSelected>>",self.on_combobox)
...
defon_combobox(self, event):
    start = self.combo1.get()
    end = self.combo2.get()
    self.shortestPath(start, end)

Note that this solution assumes that shortestPath is a method on the object. You've indented it like it is, but it isn't defined to take the self parameter. You should give it the self parameter if you want to call it as an object method:

defshortestPath(self, start, end):
    ...

There are a few other problems with the code as posted, but they are unrelated to the question that was asked. I would like to offer one other piece of advice though: avoid the use of place. It makes your GUIs harder to write and maintain than when using pack or grid, and using place makes your GUI less tolerant of differences between systems. For example, on my machine the comboboxes were chopped off because the coordinates you calculated aren't right for my machine. You typically won't have those problems with grid and pack.

Post a Comment for "Binding Two Comboboxes To One Function"