Home > Software engineering >  In tkinter, how can I use a function in class A to populate a widget in class B?
In tkinter, how can I use a function in class A to populate a widget in class B?

Time:03-15

Primary relevant code is in class EntryForm (around line 190) and class BookmarkAccess (around line 246)

I am sharing the global user_id as a means of querying specific rows of data from the database, after the login is completed.

My current workaround is to use a button on the final page, to populate the listbox (self.title.select) with the queries using the user_id generated post-launch.

I have attempted the following to no avail: inherit BookmarkAccess in EntryForm and pass the widget creation in a button command here. Also tried to parse a complete query on app launch with no user_id restriction and then populate listbox with only relevant user_id, which I also could not get working.

What I would like to do is on some prior screen (such as, immediately upon login and passing updated global user_id), populate the final listbox (self.title_select) so when I access that page the listbox is already full of the intended query rows.

Any assistance or guidance would be much appreicated.

import sqlite3 as sql
import tkinter as tk
import tkinter.ttk as ttk

BASE_FONT = ("Bookman Old Style", 10)

user_id = None


class Database:
    def __init__(self, *args, **kwargs):
        self.connection = sql.connect("testing.db")
        self.cursor = self.connection.cursor()

        try:
            self.cursor.execute(
                """CREATE TABLE IF NOT EXISTS users
                (username TEXT NOT NULL UNIQUE, 
                password TEXT NOT NULL)
                ;"""
            )
            self.connection.commit()
        except sql.OperationalError:
            pass

        try:
            self.cursor.execute(
                """CREATE TABLE IF NOT EXISTS bookmarks
                (OwnerID INTEGER NOT NULL,
                Title TEXT NOT NULL,
                Link TEXT NOT NULL)
                ;"""
            )
            self.connection.commit()
        except sql.OperationalError:
            pass

    def add_account(self, username, password):
        self.cursor.execute("""INSERT INTO users VALUES (?,?)""", (username, password))
        self.connection.commit()

    def login_func(self, username, password):
        self.cursor.execute("SELECT password FROM users WHERE username = (?)", (username,))
        result = str(self.cursor.fetchone()).strip("'(),")
        print(result)
        if result == password:
            print("true")
            return True
        else:
            print("false")
            return False

    def get_user_id(self, username):
        self.cursor.execute("SELECT rowid FROM users WHERE username = (?)", (username,))
        cleaned_id = str(self.cursor.fetchone()).strip(" ( , ) ")
        return int(cleaned_id)

    def commit_bookmark(self, active_id, title, link):
        if len(title) and len(link) != 0:
            self.cursor.execute("""INSERT INTO bookmarks (OwnerID,Title,Link) 
            VALUES (?,?,?)""", (active_id, title, link,))
            self.connection.commit()
        else:
            print("nothing to bookmark")

    def title_populate(self, active_id):
        self.cursor.execute("SELECT Title FROM bookmarks WHERE OwnerID = (?)", (active_id,))
        return self.cursor.fetchall()

    def bookmarks_by_title(self, param, active_id):
        self.cursor.execute("SELECT Title FROM bookmarks WHERE Title LIKE (?) AND OwnerID=(?)",
                            ('%'   param   '%', active_id,))
        return self.cursor.fetchall()


db = Database()


# complete
class LoginInterface(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        button_styling = ttk.Style()
        button_styling.configure("my.TButton", font=BASE_FONT)

        label_styling = ttk.Style()
        label_styling.configure("my.TLabel", font=BASE_FONT)

        tk.Tk.wm_title(self, "Login Screen")

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}

        for F in (Login,
                  CreateNew,
                  EntryForm,
                  BookmarkAccess):
            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(Login)

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()


# complete
class Login(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.label1 = ttk.Label(self, text="Username: ", style="my.TLabel")
        self.label1.grid(row=1, column=1)

        self.username = ttk.Entry(self)
        self.username.grid(row=1, column=2)

        self.label2 = ttk.Label(self, text="Password: ", style="my.TLabel")
        self.label2.grid(row=2, column=1, pady=10)

        self.password = ttk.Entry(self, show="*")
        self.password.grid(row=2, column=2, pady=10)

        def login_call(event):
            if db.login_func(self.username.get(), self.password.get()) is False:
                print("failed validation")
            else:
                print("validation passed")
                global user_id
                user_id = db.get_user_id(self.username.get())
                controller.show_frame(EntryForm)

        self.login = ttk.Button(
            self, text="Login", style="my.TButton", command=lambda: login_call(Login))
        self.login.grid(row=3, column=2)

        self.create_new = ttk.Button(
            self,
            text="Create New Account",
            style="my.TButton",
            command=lambda: controller.show_frame(CreateNew),
        )
        self.create_new.grid(row=4, column=2, pady=10)


# complete
class CreateNew(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.label1 = ttk.Label(self, text="Set Username:", style="my.TLabel")
        self.label1.grid(row=1, column=1)
        self.username = ttk.Entry(self)
        self.username.grid(row=1, column=2)

        self.label2 = ttk.Label(self, text="Set Password:", style="my.TLabel")
        self.label2.grid(row=2, column=1, padx=5, pady=5)
        self.password = ttk.Entry(self)
        self.password.grid(row=2, column=2)

        self.create_button = ttk.Button(
            self,
            text="Complete New Account",
            style="my.TButton",
            command=lambda: db.add_account(self.username.get(), self.password.get()),
        )
        self.create_button.grid(row=3, column=2, padx=5, pady=5)

        self.home = ttk.Button(
            self,
            text="Go to Login",
            style="my.TButton",
            command=lambda: controller.show_frame(Login),
        )
        self.home.grid(row=5, column=2, padx=5, pady=5)


# functional
class EntryForm(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        # FIXME
        def view_bookmarks(event):
            print(user_id)
            # BookmarkAccess.title_query = db.title_populate(user_id)
            # BookmarkAccess.title_choices = tk.StringVar(value=BookmarkAccess.title_query)
            # BookmarkAccess.title_select = tk.Listbox(self, listvariable=BookmarkAccess.title_choices)
            # BookmarkAccess.title_select.grid(row=2, column=0, padx=5, pady=10)
            controller.show_frame(BookmarkAccess)

        def add_bookmark(event):
            print(user_id)
            db.commit_bookmark(user_id, self.title.get(), self.link.get(), )
            self.title.delete(0, tk.END)
            self.link.delete(0, tk.END)

        self.title_label = ttk.Label(self, text="Title: ")
        self.title_label.grid(row=0, column=0)

        self.link_label = ttk.Label(self, text="Link: ")
        self.link_label.grid(row=0, column=1)

        self.title = ttk.Entry(self)
        self.title.grid(row=1, column=0, padx=5, pady=10)

        self.link = ttk.Entry(self)
        self.link.grid(row=1, column=1, padx=5, pady=10)

        self.view_bookmarks = ttk.Button(
            self,
            text="View Bookmarks",
            style="my.TButton",
            command=lambda: view_bookmarks(Login),
        )
        self.view_bookmarks.grid(row=5, column=1)

        self.commit_new_bookmark = ttk.Button(
            self,
            text="Add Bookmark",
            style="my.TButton",
            command=lambda: add_bookmark(Login),
        )
        self.commit_new_bookmark.grid(row=2, column=1)


# functional
class BookmarkAccess(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.title_label = ttk.Label(self, text="Filter by Title: ")
        self.title_label.grid(row=0, column=0)

        # title filter: entry box
        self.title_filter = ttk.Entry(self)
        self.title_filter.grid(row=1, column=0, padx=5, pady=10)

        # FIXME
        self.title_select = tk.Listbox(self)
        self.title_select.grid(row=3, column=0, padx=5, pady=10)

        # title filter
        def title_filtering(event):
            self.title_select.destroy()
            self.title_query = db.bookmarks_by_title(self.title_filter.get(), user_id)
            self.title_choices = tk.StringVar(value=self.title_query)
            self.title_select = tk.Listbox(self, listvariable=self.title_choices)
            self.title_select.grid(row=3, column=0, padx=5, pady=10)

        self.title_filter.bind('<Return>', title_filtering)

        self.title_button = ttk.Button(self, style='my.TButton', text="Filter Title",
                                       command=lambda: title_filtering(Login))
        self.title_button.grid(row=2, column=0, padx=5, pady=10)


app = LoginInterface()
app.mainloop()

CodePudding user response:

Suggest to notify raised frame using tkinter virtual event, so that the frame can perform some action upon receiving that virtual event:

class LoginInterface(tk.Tk):
    ...

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()
        frame.event_generate('<<Raised>>') # notify frame

Then modify BookmarkAccess to populate the listbox upon receiving the virtual event:

class BookmarkAccess(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        self.title_label = ttk.Label(self, text="Filter by Title: ")
        self.title_label.grid(row=0, column=0)

        # title filter: entry box
        self.title_filter = ttk.Entry(self)
        self.title_filter.grid(row=1, column=0, padx=5, pady=10)

        self.title_choices = tk.StringVar()
        self.title_select = tk.Listbox(self, listvariable=self.title_choices)
        self.title_select.grid(row=3, column=0, padx=5, pady=10)

        self.title_filter.bind('<Return>', self.title_filtering)

        self.title_button = ttk.Button(self, style='my.TButton', text="Filter Title",
                                       command=self.title_filtering)
        self.title_button.grid(row=2, column=0, padx=5, pady=10)

        self.bind('<<Raised>>', self.title_filtering) # respond virtual event

    # title filter
    def title_filtering(self, event=None):
        self.title_query = db.bookmarks_by_title(self.title_filter.get(), user_id)
        self.title_choices.set(self.title_query)

Note that I have changed nested function title_filtering() to class method id order to be used in different situations. Also change to update the list box instead of destroy it and recreate new list box.

  • Related