在 tkinter 中,如何使用 class A 中的函数来填充 class B 中的小部件?

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

主要相关代码在classEntryForm(第190行左右)和classBookmarkAccess(第190行左右)第 246 行)

我正在共享 global user_id 作为一种在登录完成后从数据库中查询特定数据行的方法。

我目前的解决方法是在最后一页上使用一个按钮,使用 user_id 生成的 post-launch.

我已尝试以下操作但无济于事:在 EntryForm 中继承 BookmarkAccess 并在此处通过按钮命令传递小部件创建。还尝试在没有 user_id 限制的情况下解析有关应用程序启动的完整查询,然后仅使用相关的 user_id 填充列表框,我也无法正常工作。

我想做的是在某些先前的屏幕上(例如,在登录并传递更新的全局 user_id 后立即),填充最终的列表框 (self.title_select),这样当我访问它时页列表框中已经充满了预期的查询行。

非常感谢任何帮助或指导。

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()

建议使用 tkinter 虚拟事件通知引发的框架,以便框架可以在收到该虚拟事件后执行一些操作:

class LoginInterface(tk.Tk):
    ...

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

然后修改 BookmarkAccess 以在收到虚拟事件时填充列表框:

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([x[0] for x in self.title_query])

请注意,我已将嵌套函数 title_filtering() 更改为 class 方法,以便在不同情况下使用。还更改为更新列表框而不是销毁它并重新创建新的列表框。