6

I have a tkinter interface where I need to display some query results and I need for the user to be able to modify a column and submit the results. Currently to pull the queries I'm doing something like this:

conn = connection_info_goes_here
cur = conn.cursor()
cur.execute(query_goes_here)

And this is my query:

SELECT id, reviewer, task, num_seconds, start_time, end_time
FROM hours
WHERE DATE(start_time) = '2014-12-18'
AND reviewer = 'john'

The field that the user needs to modify is num_seconds (just numbers). My question is, how do I make the query results show in the grid and how do I make one of the fields modifiable with a button to submit the changes?

Additional info: I already did this in a very messy way using exec() and programmatically creating variables for each field. It became very long and confusing and I really think there has to be a better and easier way to do this.

Any help is appreciated. Thanks!!

Quick Update: since this was put on hold, i'll add an image of something similar to what I'm looking for:

enter image description here

The values in the entry label must replace the values in the column to the right when I upload them back to the DB.

When I say I did this in a messy way, is because I did (the only way I could think of):

def cor_window():
    corrections = Tk()
    corrections.title("Corrections")
    corrections_frame = ttk.Frame(corrections)

    cor_values = []
    count=0
    cor_count=0
    for x in results:
        count2=0
        for y in results[count]:

            if count2 == 3:
                exec('int' + str(cor_count) + '=tkinter.StringVar')
                exec('int' + str(cor_count) + '_entry = ttk.Entry(corrections, width=20, textvariable=int' + str(cor_count) + ')')
                exec('int' + str(cor_count) + '_entry.grid(column=count2, row=count+2)')

                cor_count = cor_count+1
                cor_values.append('int' + str(cor_count) + '_entry')

                ttk.Label(corrections, width=20, anchor=CENTER, relief=SUNKEN, borderwidth=1, text= results[count][count2]).grid(column=count2+1, row=count+2)

            elif count2 > 3:
                ttk.Label(corrections, width=20, anchor=CENTER, relief=SUNKEN, borderwidth=1, text= results[count][count2]).grid(column=count2+1, row=count+2)
            else:
                ttk.Label(corrections, width=20, anchor=CENTER, relief=SUNKEN, borderwidth=1, text= results[count][count2]).grid(column=count2, row=count+2)
            count2=count2+1
        count=count+1

    ttk.Button(corrections, text="Done!", command=upload_cor).grid(column=0, row=1)

Where results is the list that contains the query results and upload_cor is the function the will upload the changes to the DB. Since I used exec, even if the user modifies the entry box, I can't use .get() to get what the user typed. When I try use .get(), I only get None even if something was typed in the entry box.

I just need a different method to do this, again, any ideas are welcome.

rodrigocf
  • 1,951
  • 13
  • 39
  • 62
  • num_seconds would use an Entry (or a list of Entry IDs) and the rest would be Labels. You would also have to link the row number to the record and compare each entry to the original value to see which rows have changed, but if "id" is unique then it should be simple. Entry widget info http://effbot.org/tkinterbook/entry.htm –  Dec 19 '14 at 01:31

1 Answers1

9

You definitely don't want to use exec, and you don't need to use the textvariable option. Both of those just add to the confusion. Simply store your widgets as a dictionary, get the data straight from the entry widget, and it all becomes very easy to manage.

Here's a working example:

import tkinter as tk

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        b = tk.Button(self, text="Done!", command=self.upload_cor)
        b.pack()
        table = tk.Frame(self)
        table.pack(side="top", fill="both", expand=True)

        data = (
            (45417, "rodringof", "CSP L2 Review", 0.000394, "2014-12-19 10:08:12", "2014-12-19 10:08:12"),
            (45418, "rodringof", "CSP L2 Review", 0.000394, "2014-12-19 10:08:12", "2014-12-19 10:08:12"),
            (45419, "rodringof", "CSP L2 Review", 0.000394, "2014-12-19 10:08:12", "2014-12-19 10:08:12"),
            (45420, "rodringof", "CSP L2 Review", 0.000394, "2014-12-19 10:08:12", "2014-12-19 10:08:12"),
            (45421, "rodringof", "CSP L2 Review", 0.000394, "2014-12-19 10:08:12", "2014-12-19 10:08:12"),
            (45422, "rodringof", "CSP L2 Review", 0.000394, "2014-12-19 10:08:12", "2014-12-19 10:08:12"),
            (45423, "rodringof", "CSP L2 Review", 0.000394, "2014-12-19 10:08:12", "2014-12-19 10:08:12"),
        )

        self.widgets = {}
        row = 0
        for rowid, reviewer, task, num_seconds, start_time, end_time in (data):
            row += 1
            self.widgets[rowid] = {
                "rowid": tk.Label(table, text=rowid),
                "reviewer": tk.Label(table, text=reviewer),
                "task": tk.Label(table, text=task),
                "num_seconds_correction": tk.Entry(table),
                "num_seconds": tk.Label(table, text=num_seconds),
                "start_time": tk.Label(table, text=start_time),
                "end_time": tk.Label(table, text=start_time)
            }

            self.widgets[rowid]["rowid"].grid(row=row, column=0, sticky="nsew")
            self.widgets[rowid]["reviewer"].grid(row=row, column=1, sticky="nsew")
            self.widgets[rowid]["task"].grid(row=row, column=2, sticky="nsew")
            self.widgets[rowid]["num_seconds_correction"].grid(row=row, column=3, sticky="nsew")
            self.widgets[rowid]["num_seconds"].grid(row=row, column=4, sticky="nsew")
            self.widgets[rowid]["start_time"].grid(row=row, column=5, sticky="nsew")
            self.widgets[rowid]["end_time"].grid(row=row, column=6, sticky="nsew")

        table.grid_columnconfigure(1, weight=1)
        table.grid_columnconfigure(2, weight=1)
        # invisible row after last row gets all extra space
        table.grid_rowconfigure(row+1, weight=1)

    def upload_cor(self):
        for rowid in sorted(self.widgets.keys()):
            entry_widget = self.widgets[rowid]["num_seconds_correction"]
            new_value = entry_widget.get()
            print("%s: %s" % (rowid, new_value))

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()

I would actually implement this a little differently by creating a Table class with an add_row method, but I didn't want to get too complex. The basic idea is the same whether you create a Table class, do it all in the one class, or do it procedurally - create a dictionary to represent your data. You could use nested lists too, but I find dictionaries to be much easier to use. They are also self-documenting, since you reference things by a symbolic name rather than just knowing that column 4 is the start time.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • This is actually pretty awesome, I think understand this now from a different point of view. Just have one more question, how do I put a scrollbar in case the results are bigger than the window? is that possible? Thanks!!!! – rodrigocf Dec 22 '14 at 22:36
  • And if should just save that for another question just let me know :) – rodrigocf Dec 22 '14 at 22:37
  • @rodrigocf: that's a separate question which has been answered before. See http://stackoverflow.com/a/3092341/7432 – Bryan Oakley Dec 22 '14 at 22:53
  • Sure, no problem, I'll award the bounty as soon as I can, it's telling me i have to wait... – rodrigocf Dec 22 '14 at 23:43