英文:
Tkinter multitabbed UI with dynamically created widgets - Not sure if this is even possible
问题
我正在开发一个多标签UI,计划创建N个独立标签用于数据库搜索查询。(请注意N是未定义的,用户可以创建任意数量的标签。)
每个数据库搜索查询都将具有相同类型的小部件,但搜索关键字和其他筛选器将不同(取决于标签)。用户应该能够进行多个查询,并能够在不需要刷新内容的情况下导航(即不需要重新运行搜索函数)。
我目前的问题是下面的代码没有产生预期的结果,而是在所有标签上显示相同的一组小部件(按钮、输入框和列表框),而不是创建与新创建的标签相对应的独立小部件集。
我选择tkinter的原因之一是因为它有框架,我希望UI能够扩展,即在窗口最大化时能够重新定位小部件。
-----------代码片段------------------------
英文:
I am working on a multi tabbed UI that is planned to have N standalone tabs created for DB search queries.( Note that N is undefined and user can create as many tabs as they want..)
Each DB search query will have same types of the widgets BUT the search key and other filters will be different (depending on the tab). A user should have a capability of having several queries and being able to navigate across without a need to refresh the content (i.e. without a need to re-run search function).
My current problem is that the code below doesn't produce the expected outcome but instead creates a single set of widgets (buttons, entry & list box) displayed on all tabs vs creating independent set of widgets mapped to newly created tab.
One of the reasons why I have chosen tkinter is that because it has frames and I wanted the UI to be scalable i.e. wanted to be able reposition widgets when the window is maximized.
-----------Code Snippet------------------------
`import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
clstk = tk.Tk()
# -----------------------------------------------------------------------------
#entries
# -----------------------------------------------------------------------------
entry_search_key = tk.StringVar()
entry_dir_out_path = tk.StringVar()
# -----------------------------------------------------------------------------
# labels:
# -----------------------------------------------------------------------------
lbl_dir_input_path = tk.StringVar()
lbl_search_for = tk.StringVar()
# -----------------------------------------------------------------------------
#colors
# -----------------------------------------------------------------------------
frame_bg_color='#EBE8F4'
btn_bg_color='#F8F7F7'
dict_config_params = dict([
('var_def_search_key', 'search key'),
])
class main(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.notebook = ttk.Notebook(self.parent)
self.setup_ui()
def setup_ui(self):
self.set_window_geometry()
self.create_menu()
def set_window_geometry(self):
#set grid layout for the entire window
# 1 column and 3 rows and weights for scaling up
tk.Grid.columnconfigure(self.parent, 0, weight=1)
tk.Grid.rowconfigure(self.parent, 0, weight=0)
tk.Grid.rowconfigure(self.parent, 1, weight=0,minsize=75)
tk.Grid.rowconfigure(self.parent, 2, weight=1)
self.notebook.grid(column=0, row=0, sticky=tk.N+tk.W)
self.notebook.grid(column=0, row=1, sticky=tk.E+tk.W+tk.N+tk.S)
def create_tabs(self):
tab_frame = ttk.Frame(self.notebook)
idx = self.notebook.index('end')
self.notebook.add(tab_frame, text ='Query #: '+str(idx+1),sticky=tk.E+tk.W+tk.N+tk.S)
self.add_btn_frame(tab_frame)
self.add_list_frame(tab_frame, idx)
def add_btn_frame(self,tab1):
#this method is to create frames to ensure widgets aren't overlapped when resizing window
mainFrame = tk.Frame(tab1,bg='blue').grid(row=1, column=0, padx=3)
#mainFrame.grid(row=0, column=0, sticky="nw")
labelsearchText = tk.Label(mainFrame,bg=frame_bg_color,
textvariable=lbl_search_for)
lbl_search_for.set(" Search for: ")
labelsearchText.grid(row=1, column=0,sticky="w")
searchEntry = tk.Entry(mainFrame,
textvariable=entry_search_key,width=50)
entry_search_key.set(dict_config_params['var_def_search_key'])
searchEntry.grid(row=1, column=0,sticky="w", padx= 80)
button = tk.Button(mainFrame, text="search", width=10,bg=btn_bg_color,
command=self.search_routine)
button.grid(row=1, column=0,sticky="w", padx= 400)
def add_list_frame(self, tabFrame,idx):
#this method is to create frames to ensure widgets aren't overlapped when resizing window
mainFrame = tk.Frame(tabFrame,).grid(row=2, column=0, padx=3)
listbox = tk.Listbox(mainFrame)
scrollbar = tk.Scrollbar(listbox, orient="vertical",command=listbox.yview)
listbox.config(yscrollcommand=scrollbar.set)
listbox.grid(row=2, column=0,padx=5, sticky="nsew")
#tab1.append(listbox)
scrollbar.pack(side="right", fill="y")
listbox.insert("end", "Welcome to search" + str(idx))
def add_sb_frame(self):
mainFrame = tk.Frame(self.parent,).grid(row=2, columnspan=1, padx=3)
def create_menu(self):
menubar = tk.Menu(self.parent)
menu_search = tk.Menu(menubar, tearoff =0)
menu_report = tk.Menu(menubar, tearoff =0)
menu_settings = tk.Menu(menubar, tearoff =0)
menu_exit = tk.Menu(menubar, tearoff =0)
menubar.add_cascade(label='Search', menu=menu_search)
menu_search.add_command(label = 'New Search', command=self.new_search)
self.parent.config(menu=menubar)
def new_search(self):
self.create_tabs()
def search_routine(self):
messagebox.showinfo("Open Report", "Code is in works..")
if __name__ == '__main__':
run = main(clstk)
clstk.mainloop()`
Attached screenshots is the best way to describe what i was expecting, but current code obviously produces only single set of 3 widgets that is shown on all tabs and there is no way to put a different key for the specific tab. The idea of repositioning widget wouldn't work as then I will loose content shown on different tabs and while I can save a value from the entry into the variable, it will be impossible to save content of N different lists and have all that info being repopulated.
Not sure if this UI is even possible using Tkinter and/or if there is a better library to achieve this goal.
答案1
得分: 1
你想要的在tkinter中是非常可行的,但是你需要使用正确的架构来使它变得容易。简而言之,创建一个用于搜索选项卡的类,然后创建尽可能多的实例。
以下示例说明了这种技巧。它定义了一个用于选项卡的类,一个具有菜单的菜单栏,该菜单允许您添加新的选项卡,然后自动创建第一个选项卡。您可以通过菜单创建任意数量的选项卡。
注意:这不一定是最佳解决方案。例如,您可以将add_tab
移到Menubar
类或其他类中,可以将do_search
移到主要或其他类中,可以将Main
设为tk.Frame
的子类,而不是tk.Tk
,以便除了多个选项卡之外还可以有多个窗口等等。但这些都不是与动态创建选项卡的核心问题相关的内容,所以我选择了一个相当简单的实现。
import tkinter as tk
from tkinter import ttk
class SearchTab(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.label = ttk.Label(self, text="Search for:")
self.entry = ttk.Entry(self, width=40)
self.search_button = ttk.Button(self, text="search", command=self.do_search)
self.results = tk.Listbox(self)
self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.results.yview)
self.results.configure(yscrollcommand=self.vsb.set)
self.label.grid(row=0, column=0, sticky="w")
self.entry.grid(row=0, column=1, sticky="ew")
self.search_button.grid(row=0, column=2, sticky="ew")
self.results.grid(row=1, column=0, columnspan=4, sticky="nsew")
self.vsb.grid(row=1, column=5, sticky="ns")
# 使用不可见的列来占据所有额外的空间
self.grid_columnconfigure(3, weight=1)
# 第二行应该在垂直方向上填满框架
self.grid_rowconfigure(1, weight=1)
def do_search(self):
querystring = self.entry.get()
self.results.delete(0, "end")
# 模拟一些结果...
for i in range(20):
self.results.insert("end", f"Result #{i+1} for '{querystring}'")
class Menubar(tk.Menu):
def __init__(self, main, **kwargs):
super().__init__(main, **kwargs)
self.search_menu = tk.Menu(self)
self.search_menu.add_command(label="Add Tab", command=main.add_tab)
self.add_cascade(label="Search", menu=self.search_menu)
class Main(tk.Tk):
def __init__(self):
super().__init__()
self.notebook = ttk.Notebook(self)
self.notebook.pack(fill="both", expand=True)
self.menubar = Menubar(self)
self.configure(menu=self.menubar)
# 添加第一个选项卡;用户可以通过菜单栏添加更多选项卡
self.add_tab()
def add_tab(self):
new_tab = SearchTab(self.notebook)
tab_number = int(self.notebook.index("end")) + 1
self.notebook.add(new_tab, text=f"Query #{tab_number}")
if __name__ == "__main__":
main = Main()
tk.mainloop()
希望这有助于您理解代码的结构和工作原理。
英文:
What you want is very doable with tkinter, but you need to use the right architecture to make it easy. In a nutshell, create a class for the search tab, and then create as many instances as you want.
The following example illustrates the technique. It defines a class for a tab, a menubar with a menu that lets you add new tabs, and then auto-creates the first tab. You can create as many tabs as you want via the menu.
Note: this isn't necessarily the best solution. You could, for example, move add_tab
into the Menubar
class or some other class, you could move do_search
into main or some other class, you could make Main
a subclass of a tk.Frame
instead of tk.Tk
so you could have multiple windows in addition to multiple tabs, and so on.
None of that is really relevant to the core question of how to dynamically create tabs so I picked a fairly simple implementation.
import tkinter as tk
from tkinter import ttk
class SearchTab(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.label = ttk.Label(self, text="Search for:")
self.entry = ttk.Entry(self, width=40)
self.search_button = ttk.Button(self, text="search", command=self.do_search)
self.results = tk.Listbox(self)
self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.results.yview)
self.results.configure(yscrollcommand=self.vsb.set)
self.label.grid(row=0, column=0, sticky="w")
self.entry.grid(row=0, column=1, sticky="ew")
self.search_button.grid(row=0, column=2, sticky="ew")
self.results.grid(row=1, column=0, columnspan=4, sticky="nsew")
self.vsb.grid(row=1, column=5, sticky="ns")
# use an invisible column to take up all extra space
self.grid_columnconfigure(3, weight=1)
# the second row should fill the frame vertically
self.grid_rowconfigure(1, weight=1)
def do_search(self):
querystring = self.entry.get()
self.results.delete(0, "end")
# simulate some results...
for i in range(20):
self.results.insert("end", f"Result #{i+1} for '{querystring}'")
class Menubar(tk.Menu):
def __init__(self, main, **kwargs):
super().__init__(main, **kwargs)
self.search_menu = tk.Menu(self)
self.search_menu.add_command(label="Add Tab", command=main.add_tab)
self.add_cascade(label="Search", menu=self.search_menu)
class Main(tk.Tk):
def __init__(self):
super().__init__()
self.notebook = ttk.Notebook(self)
self.notebook.pack(fill="both", expand=True)
self.menubar = Menubar(self)
self.configure(menu=self.menubar)
# add the first tab; the user can add more via the menubar
self.add_tab()
def add_tab(self):
new_tab = SearchTab(self.notebook)
tab_number = int(self.notebook.index("end")) + 1
self.notebook.add(new_tab, text=f"Query #{tab_number}")
if __name__ == "__main__":
main = Main()
tk.mainloop()
答案2
得分: -1
<sub>免责声明:此代码是通过多次迭代的LLM生成的。我已经测试并阅读了它,看起来还可以,但建议谨慎使用。</sub>
这几乎与您概念图片中的内容相似,但我不确定这是否完全符合您的意思,但可能对您有用。
import tkinter as tk
from tkinter import ttk
def main():
# 创建实例
root = tk.Tk()
# 添加标题
root.title("带选项卡的Tkinter GUI")
# 创建选项卡控件
tabControl = ttk.Notebook(root)
# 创建选项卡
tab1 = ttk.Frame(tabControl)
tab2 = ttk.Frame(tabControl)
# 将选项卡添加到选项卡控件
tabControl.add(tab1, text='选项卡 1')
tabControl.add(tab2, text='选项卡 2')
tabControl.pack(expand=1, fill="both")
# 为选项卡1定义布局和小部件
entry1 = ttk.Entry(tab1)
entry1.grid(column=0, row=0, sticky='ew')
button1 = ttk.Button(tab1, text="搜索")
button1.grid(column=1, row=0)
listbox1 = tk.Listbox(tab1)
listbox1.grid(column=0, row=1, columnspan=2, sticky='nsew')
listbox1.insert(tk.END, "欢迎使用搜索") # 在列表框中插入文本
# 配置网格
tab1.grid_columnconfigure(0, weight=1)
tab1.grid_rowconfigure(1, weight=1)
# 为选项卡2定义布局和小部件
entry2 = ttk.Entry(tab2)
entry2.grid(column=0, row=0, sticky='ew')
button2 = ttk.Button(tab2, text="搜索")
button2.grid(column=1, row=0)
listbox2 = tk.Listbox(tab2)
listbox2.grid(column=0, row=1, columnspan=2, sticky='nsew')
listbox2.insert(tk.END, "欢迎使用搜索 2") # 在列表框中插入文本
# 配置网格
tab2.grid_columnconfigure(0, weight=1)
tab2.grid_rowconfigure(1, weight=1)
# 启动主循环
root.mainloop()
if __name__ == "__main__":
main()
英文:
<sub>Disclaimer: this code was generated through a few iterations of an LLM. I tested it and read it and it looks okay, but caution is advised.</sub>
This is pretty close to what's in your concept pictures, I'm not entirely sure if this is exactly what you meant but it might be useful to you.
import tkinter as tk
from tkinter import ttk
def main():
# Create instance
root = tk.Tk()
# Add a title
root.title("Tkinter GUI with Tabs")
# Create tab control
tabControl = ttk.Notebook(root)
# Create the tabs
tab1 = ttk.Frame(tabControl)
tab2 = ttk.Frame(tabControl)
# Add the tabs to the tab control
tabControl.add(tab1, text='Tab 1')
tabControl.add(tab2, text='Tab 2')
tabControl.pack(expand=1, fill="both")
# Define layout and widgets for Tab 1
entry1 = ttk.Entry(tab1)
entry1.grid(column=0, row=0, sticky='ew')
button1 = ttk.Button(tab1, text="Search")
button1.grid(column=1, row=0)
listbox1 = tk.Listbox(tab1)
listbox1.grid(column=0, row=1, columnspan=2, sticky='nsew')
listbox1.insert(tk.END, "Welcome to Search") # Insert text into listbox
# Configure the grid
tab1.grid_columnconfigure(0, weight=1)
tab1.grid_rowconfigure(1, weight=1)
# Define layout and widgets for Tab 2
entry2 = ttk.Entry(tab2)
entry2.grid(column=0, row=0, sticky='ew')
button2 = ttk.Button(tab2, text="Search")
button2.grid(column=1, row=0)
listbox2 = tk.Listbox(tab2)
listbox2.grid(column=0, row=1, columnspan=2, sticky='nsew')
listbox2.insert(tk.END, "Welcome to Search 2") # Insert text into listbox
# Configure the grid
tab2.grid_columnconfigure(0, weight=1)
tab2.grid_rowconfigure(1, weight=1)
# Start the main loop
root.mainloop()
if __name__ == "__main__":
main()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论