Python ttk notebook showing selected tab wrongly - python

from tkinter import *
from tkinter import ttk
class MainGame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def tab_change(self, event):
tab_id = self.page.index('current')
print(tab_id, self.page.tab(tab_id, 'text'))
def initUI(self):
global canvas
self.parent.title('PythonPage')
self.pack(fill = BOTH, expand = 1)
self.page = ttk.Notebook(self, width = 646 ,height = 629)
self.page1 = Frame(self)
self.page2 = Frame(self)
self.page.add(self.page1, text = 'Tab1')
self.page.add(self.page2, text = 'Tab2')
self.page.bind('<ButtonPress-1>', self.tab_change)
self.page.pack(expand = 0, anchor = 'w', side = 'top')
root = Tk()
root.geometry('925x650')
main = MainGame(root)
root.mainloop()
tab_change can show their id and names, but not correctly.
When Tab1 is clicked, I clicked Tab2 but it still print 0 Tab1, it needs one more click to print 1 Tab2 .
Tab2 click to Tab1 is the same, it needs one more click to show the current selected tab.
I want to find why the tabs need double click? And how can I get selected tab correctly by a single click?

Change:
self.page.bind('<ButtonPress-1>', self.tab_change)
To:
self.page.bind('<ButtonRelease-1>', self.tab_change)
Because unless you have released the pressed button, the tab hasn't changed!

Related

Tkinter Notebook create new tabs by clicking on a plus/+ tab like every web browser

I'm trying to create new tabs in my navigation bar notebook by clicking on the last tab. To further complicate the task my application is written with classes. My current and less elegant solution requires an Entry with a Button to create the new tab with the title entered in the Entry widget. Does anyone know a more elegant solution to my problem?
Heres my code:
import tkinter as tk
from tkinter import ttk
class MainApp(tk.Tk):
"""Main window class"""
def __init__(self):
super(MainApp, self).__init__()
self.geometry("1000x1000")
self.main_window = tk.Frame(self)
self.main_window.pack(side="top", fill="both", expand=True)
# saving all tabs & Frames to a dictionary to be able to access them later
self.frames = {}
self.tabs = {}
# create a tab bar
self.navbar = Navbar(self.main_window)
# set the layout
self.set_layout()
def set_layout(self):
"""creates the default app layout"""
self.add_tab("Settings") # first page
self.add_tab("Case 1")
def add_tab(self, title):
"""adds a new tab with name title to the navbar"""
tab = ttk.Frame(self)
self.navbar.add(tab, text=title)
self.tabs[title] = tab
# check if Settings or Case type Tab, to create MainInput frame with correct buttons
if title.lower().find("setting") != -1:
self.frames[title] = MainInput(self, tab, self.navbar, settings=True)
else:
self.frames[title] = MainInput(self, tab, self.navbar, case=True)
self.navbar.pack(fill=tk.BOTH, expand=tk.YES)
for tab in self.tabs:
self.frames[tab].grid(sticky="nesw")
def add_frame(self, title, frame):
"""adds the frame to frames dict with key title"""
self.frames[title] = frame
class Navbar(ttk.Notebook):
"""returns a Notebook"""
def __init__(self, parent):
ttk.Notebook.__init__(self, parent)
#staticmethod
def delete(tab):
"""delete current tab"""
tab.forget(tab.select())
class MainInput(tk.Frame):
"""The base frame of every tab"""
def __init__(self, root, parent, notebook, settings=False, case=False):
tk.Frame.__init__(self, parent)
# Either build a settings or testcase tab
if settings is True:
SettingsField(root, parent)
if case is True:
CaseGeneral(parent, notebook)
class SettingsField(tk.Frame):
"""Creates a settings tab"""
def __init__(self, root, parent):
tk.Frame.__init__(self, parent)
# add the "new tab" name entry and button
tk.Label(parent, text="Add new Testcase:").grid(row=0, column=0, columnspan=2, sticky="w")
tk.Label(parent, text="Name:").grid(row=1, column=0, sticky="w", padx=20)
new_tab_title = tk.Entry(parent, textvariable=tk.StringVar(), width=30)
new_tab_title.grid(row=1, column=1, columnspan=2, sticky="w")
add_button = tk.Button(parent, text="add", command=lambda: [root.add_tab(new_tab_title.get())])
add_button.grid(row=1, column=3, sticky="w", padx=5)
class CaseGeneral(tk.Frame):
def __init__(self, parent, notebook):
tk.Frame.__init__(self, parent)
# create "delete current tab" buton
tk.Label(parent, text="delete Testcase").grid(row=0, column=0)
delete_button = tk.Button(parent, text="delete", command=lambda: [Navbar.delete(notebook)])
delete_button.grid(row=0, column=1, sticky="w")
Images of the running code:
Settings Tab
Cases
Note: Please don't roast me since this is my first post here :)
A really simple solution would be to create a binding on <<NotebookTabChanged>> that creates a new tab whenever the last tab on the row is selected. You can then create a tab with a "+" as the label and make sure it's the last tab.
Here's a simple example of the technique. It gives the new tab a label of <untitled>. Every time you click on the last tab it creates a new tab immediately before the last tab and then selects it.
import tkinter as tk
from tkinter import ttk
def handleTabChange(event):
if notebook.select() == notebook.tabs()[-1]:
index = len(notebook.tabs())-1
frame = tk.Frame(notebook)
notebook.insert(index, frame, text="<untitled>")
notebook.select(index)
root = tk.Tk()
notebook = ttk.Notebook(root)
notebook.bind("<<NotebookTabChanged>>", handleTabChange)
notebook.pack(fill="both", expand=True)
# add a tab that creates new tabs when selected
frame = tk.Frame()
notebook.add(frame, text="+")
root.mainloop()

Changing tab widgets from another tab in Tkinter

I am creating a GUI using tkinter based on the structure described here. I have some tabs that look identical but with different variables. So I decided to define a class for tabs and add them to the main window. I am going to configure some widgets in one tab from another tab. In line 11, a function is defined that when a button in tab_2 is clicked, tab_1's button background color changes to green. Whereas its working, I have two question:
Is it possible not to define channel_1 as an attribute of main_window? I think there must be better way to do so, specifically, if the GUI is going to be used as module (then main_window will not be defined).
Is it possible to know which tab is open, so when button in each tab is clicked, configurations in the other one changes only?
import tkinter as tk
from tkinter import ttk
class Channel(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.btn = tk.Button(self.parent, text = 'click me', command = self.change_green)
self.btn.pack()
def change_green(self):
main_window.channel_1.btn.config(bg = 'green') # line 11
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.tab_control = ttk.Notebook(self.parent)
self.tab_1 = ttk.Frame(self.tab_control)
self.tab_2 = ttk.Frame(self.tab_control)
self.tab_control.add(self.tab_1, text = 'tab 1')
self.tab_control.add(self.tab_2, text = 'tab 2')
self.tab_control.pack(fill = 'both', expand = 1)
self.channel_1 = Channel(self.tab_1)
self.channel_2 = Channel(self.tab_2)
if __name__ == "__main__":
root = tk.Tk()
main_window = MainApplication(root) # <<<< here defined main_window
main_window.pack(side="top", fill="both", expand=True)
root.mainloop()
I would create class MyTab and keep its widgets in this class, not in channel. It can also keep access to other tab(s) to button in one tab can change color in other tab.
Using tab's parent (self.master) I can get active tab, list of all tabs and activate other tab.
import tkinter as tk
from tkinter import ttk
class MyTab(tk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
#self.master = master # super() already set it
self.btn = tk.Button(self, text='click me', command=self.change_green)
self.btn.pack()
self.other_tab = None # default value at start
def change_green(self):
if self.other_tab:
# change color in other tab
self.other_tab.btn.config(bg = 'green')
# get active tab ID
print('active tab ID:', self.master.select())
# get button in active tab
active_tab = root.nametowidget(self.master.select())
print('active tab - btn text:', active_tab.btn['text'])
# get all tabs
print('all tabs:', self.master.children.items())
# set other tab as active
self.master.select(self.other_tab)
class MainApplication(tk.Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
#self.master = master # super() already set it
self.tab_control = ttk.Notebook(self.master)
self.tab_1 = MyTab(self.tab_control)
self.tab_2 = MyTab(self.tab_control)
self.tab_1.other_tab = self.tab_2
self.tab_2.other_tab = self.tab_1
self.tab_control.add(self.tab_1, text = 'tab 1')
self.tab_control.add(self.tab_2, text = 'tab 2')
self.tab_control.pack(fill = 'both', expand = 1)
if __name__ == "__main__":
root = tk.Tk()
main_window = MainApplication(root)
main_window.pack(side="top", fill="both", expand=True)
root.mainloop()

filling individual frames in tkinter notebook Tabs with widgets (after automatic tab generation)

I found a way makeing it easy to automatically generate multiple Tabs with the ttk notebook. As a "solution" to: http://stackoverflow.com/questions/39175898/automatic-multiple-tab-generation-with-tkinter-notebook?noredirect=1#comment65695477_39175898
But now I have a problem to fill the Tabs with individual content namely widgets. What do I need for "???" in the commented out part?
I would appreciate it very much if you show me how to solve this.
from tkinter import *
from tkinter import ttk
###
class MyTab(Frame):
def __init__(self, root, name):
Frame.__init__(self, root)
self.root = root
self.name = name
###
class Application():
def __init__(self):
self.tabs = {'ky': 1}
print(self.tabs['ky'])
self.root = Tk()
self.root.minsize(300, 300)
### Tab generation
self.notebook = ttk.Notebook(self.root, width=800, height=550)
tab_names = ["info", "S00", "S01", "S02", "S03", "S04", "S05", "S06", "S07", "S08", "help"]
for i in range(0, len(tab_names)):
tab = MyTab(self.notebook, tab_names[i])
self.notebook.add(tab, text=tab_names[i])
self.button = Button(self.root, text='next ->', command=self.next_Tab).pack(side=BOTTOM)
### info Tab Widgets
#self.btn1 = Button(???, text='Info Button', command=self.next_Tab).pack(side=RIGHT)
### S00 Tab Widgets
#self.btn2 = Button(???, text="test_btn")
#self.btn2.pack()
### S01 Tab Widgets and so on...
self.notebook.pack(side=TOP)
def next_Tab(self):
print("next Tab -> not yet defined")
def run(self):
self.root.mainloop()
###
Application().run()
The rule is quite simple: to place a widget in another widget, you need a reference to the other widget. In your case, the simple thing to do is create a dictionary to hold references to your frames:
tabs = {}
for i in range(0, len(tab_names)):
tab = MyTab(self.notebook, tab_names[i])
self.notebook.add(tab, text=tab_names[i])
tabs[tab_names[i]] = tab
...
self.btn1 = Button(tabs["info"], ...)
By the way, you can make your loop more readable (and more "pythonic") by directly iterating over the list of tab names rather than iterating over the index values:
tabs = {}
for tab_name in tab_names:
tab = MyTab(self.notebook, tab_name)
self.notebook.add(tab, text=tab_name)
tabs[tab_name] = tab

Tkinter focus_set() does not work on text widget

I am using Tkinter with python for a simple UI where I have a text widget and a button. When the button is pressed I want the cursor focus to be set on the text widget, using focus_set() method.
I don't understand why the focus_set() is not working in my code. I think it might be because my text widget is within a frame (frame1) and I cannot properly access this widget in my startPaus() method. Any ideas how I can fix the problem??
class TypingField(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.submit_tog = True
self.initUI()
def initUI(self):
self.parent.title("Text Field")
self.pack(fill = BOTH, expand=True)
frame1 = Frame(self, width = 50, height =25)
frame1.pack(fill = X, expand=True)
self.scroll = Scrollbar(frame1)
self.scroll.pack(side = "right", fill = Y)
self.text = Text(frame1)
self.text.pack(fill=Y)
self.scroll.config(command=self.text.yview)
self.text.config(yscrollcommand=self.scroll.set)
frame2 = Frame(self)
frame2.pack(fill=X, expand=True)
self.submit = Button(frame2,text="Start Test")
self.submit.bind("<Button-1>", self.startPause)
self.submit.pack(fill=X)
def startPause(self, event):
if self.submit_tog:
self.submit.configure(text = "Pause")
self.text.focus_set()
else:
self.submit.configure(text = "Start Test")
self.submit_tog = not self.submit_tog
The following works on my machine. Note that the focus is always on the Text widget for the code you posted, enter something to see this, because the focus is never set elsewhere, but in the code below it alternates between the Text widget and the Button to illustrate.
class TypingField():
def __init__(self, parent):
self.parent = parent
self.submit_tog = True
self.initUI()
def initUI(self):
self.parent.title("Text Field")
frame1 = Frame(self.parent, width = 50, height =25)
frame1.pack(fill = X, expand=True)
self.scroll = Scrollbar(frame1)
self.scroll.pack(side = "right", fill = Y)
self.text = Text(frame1)
self.text.pack(fill=Y)
self.scroll.config(command=self.text.yview)
self.text.config(yscrollcommand=self.scroll.set)
frame2 = Frame(self.parent)
frame2.pack(fill=X, expand=True)
self.submit = Button(frame2,text="Start Test")
self.submit.bind("<Button-1>", self.startPause)
self.submit.pack(fill=X)
def startPause(self, event):
if self.submit_tog:
self.submit.configure(text = "Text Focus")
self.text.focus_set()
else:
self.submit.configure(text = "Button Focus")
self.submit.focus_set()
self.submit_tog = not self.submit_tog
root=Tk()
TypingField(root)
root.mainloop()

Clearing specific widgets in tkinter

I am attempting to have a function in python that clears the screen upon a button being pressed. I am aware of grid_remove but am unsure of how to use it. Also is there a way to clear everything from a specific function, ie both "hi" and "clear"?
from tkinter import *
class Movies:
def __init__(self, master):
hi = Label(text = "Hello")
hi.grid(row = 0, column = 0)
clear = Button(text = "Click", command=self.clear)
clear.grid(row = 1, column = 0)
def clear(self):
hi.grid_remove()
root = Tk()
gui = Movies(root)
root.geometry("100x200+0+0")
root.mainloop()
You could use the built in winfo_children method if you're just wanting to toggle hiding / showing all of the widgets in whatever parent holds the widgets. Small example:
from tkinter import *
class Movies:
def __init__(self, master):
self.master = master
self.state = 1
for i in range(5):
Label(self.master, text='Label %d' % i).grid(row=0, column=i)
self.magic_btn = Button(self.master, text='Make the Magic!',
command=self.magic)
self.magic_btn.grid(columnspan=5)
def magic(self):
self.state = not self.state
for widget in self.master.winfo_children(): #iterate over all child widgets in the parent
#Comment out to clear the button too, or leave to toggle widget states
if widget != self.magic_btn: #or some other widget you want to stay shown
if self.state:
widget.grid()
else:
widget.grid_remove()
print(self.state)
root = Tk()
gui = Movies(root)
root.mainloop()

Categories