odd behavior in tkinter.tk.Sizegrip - python

I'm currently trying to learn tkinter in Python 3 so I'm not sure if I'm looking at a bug or I'm not doing things correctly.
from tkinter import *
from tkinter import ttk
root = Tk()
grip = ttk.Sizegrip(root).grid(column=0, row=0, sticky=(S,E))
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.mainloop()
When the Sizegrip is grabbed the whole window moves rapidly (faster than my mouse pointer is moving) to the bottom of the screen. The window is being appropriately resized but the movement of the whole window is not what I would expect. [I'm using Ubuntu 10.04 with Python 3.1.2]

I have the same problem on my system (Ubuntu 10.10, Python 2.6) and I think it has to do with the window manager and not Tkinter. When I use Openbox, I don't have the problem, and when I use root.overrideredirect(1), I don't have the problem either.
What you can do is make a ttk.Label themed to look like a sizegrip. Bind it to and resize the window accordingly. Here's a window with a sizegrip made this way:
from tkinter import *
from tkinter import ttk
#
# Callbacks:
#
# Change "bottom_right_corner" to "size_nw_se" and
# "arrow" to "left_ptr" if running on Windows.
#
def button_press(event):
sizegrip["cursor"] = "bottom_right_corner"
def resize(event):
deltax = event.x_root - root.winfo_rootx()
deltay = event.y_root - root.winfo_rooty()
if deltax < 1:
deltax = 1
if deltay < 1:
deltay = 1
root.geometry("%sx%s" % (deltax, deltay))
def button_release(event):
sizegrip["cursor"] = "arrow"
# Widget Creation
root = Tk()
sizegrip = ttk.Label(root, style="Sizer.TLabel")
# Styling
style = ttk.Style()
style.layout("Sizer.TLabel", [("Sizegrip.sizegrip",
{"side": "bottom", "sticky": "se"})])
# Geometry Management
sizegrip.pack(side="bottom", anchor="se")
# Bindings
sizegrip.bind("<ButtonPress-1>", button_press)
sizegrip.bind("<B1-Motion>", resize)
sizegrip.bind("<ButtonRelease-1>", button_release)
root.mainloop()
I am used to Python 2, so sorry if I've messed up a bit on the syntax. I tested it with Python 2 imports ("from Tkinter import *", and "import ttk") and it works. Let's just hope the imports are all that's different in Python 3.

Related

App's icon is not show in taskbar because of Custom title bar?

I am creating an code editor in which I want to custom title bar to match my app theme and I have created an custom title bar but my app is not showing in taskbar
If any external libraries are for this, Please tell me
What libraries I have to learn to solve my problem please tell me
how to show app icon on taskbar, Actually I have no idea about it
if you can solve it
Please help me to solve my problem
this is my full code(not full code but short version of real one):-
from tkinter import*
def move(e):
xwin = root.winfo_x()
ywin = root.winfo_y()
startx = e.x_root
starty = e.y_root
ywin -= starty
xwin -= startx
def move_(e):
root.geometry(f"+{e.x_root + xwin}+{e.y_root + ywin}")
startx = e.x_root
starty = e.y_root
frame.bind("<B1-Motion>",move_)
def minieme1_(event=None):
root.update_idletasks()
root.overrideredirect(False)
root.state("iconic")
def frame_map(event=None):
root.update_idletasks()
root.overrideredirect(True)
root.state("normal")
root.call()
def minimefunction(event=None):
global size
if size:
root.geometry(f"{screen_width}x{screen_height-40}+0+0")
minimsi.config(text=" \u2752 ")
size = False
else:
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
minimsi.config(text=" \u25a0 ")
size = True
def quitApp():
root.destroy()
def close_blink(event=None):
close_button.config(bg="red")
def close_blink1(event=None):
close_button.config(bg="gray19")
def minimsi_blink(event=None):
minimsi.config(bg="gray29")
def minimsi_blink1(event=None):
minimsi.config(bg="gray19")
def minimsi1_blink(event=None):
minimsi1.config(bg="gray29")
def minimsi1_blink1(event=None):
minimsi1.config(bg="gray19")
root = Tk()
size = True
app_width = 600
app_height = 500
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
print(screen_width,screen_height)
x = (screen_width/2) - (app_width/2)
y = (screen_height/2) - (app_height/2)
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
root.overrideredirect(True)
frame = Frame(root,bg="gray29")
Label(frame,text="My App",font="Consolas 15",bg="gray29",fg="white").pack(side=LEFT,padx=10)
close_button = Button(frame,text=" X ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=quitApp)
close_button.pack(side=RIGHT)
minimsi = Button(frame,text=" \u25a0 ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minimefunction)
minimsi.pack(side=RIGHT)
minimsi1 = Button(frame,text=" - ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minieme1_)
minimsi1.pack(side=RIGHT)
frame.pack(fill=X)
yscroll = Scrollbar(orient=VERTICAL)
yscroll.pack(side=RIGHT,fill=Y)
editor = Text(font="Consolas 15",bg="gray19",fg="white",insertbackground="white",borderwidth=0,yscrollcommand=yscroll.set)
yscroll.config(command=editor.yview)
editor.pack(expand=True,fill=BOTH)
root.config(bg="gray19")
frame.bind("<Button-1>",move)
frame.bind("<B1-Motion>",move)
# minimsi1.bind("<Button-1>",minieme1_)
frame.bind("<Map>",frame_map)
close_button.bind("<Enter>",close_blink)
close_button.bind("<Leave>",close_blink1)
minimsi.bind("<Enter>",minimsi_blink)
minimsi.bind("<Leave>",minimsi_blink1)
minimsi1.bind("<Enter>",minimsi1_blink)
minimsi1.bind("<Leave>",minimsi1_blink1)
root.mainloop()
You can see the problem in this image:-
Disclaimer, this may not be the best approach to achieve OP`s goal, but I'm sticking to this example because:
There are several question with this example on StackOverflow
It shows a basic knowledge that is important for deeper digging.
The improvement of this code to the original is that the style applies again after you iconify it and bring that windows back up.
See the twitch of this example is how the taskbar keeps track of the application. There is a good article of Raymond Chen (Ms-Developer) where he quotes this:
“If you want to dynamically change a window’s style to one that
doesn’t support visible taskbar buttons, you must hide the window
first (by calling ShowWindow with SW_HIDE), change the window style,
and then show the window.”
And also points out a weak point of some programs and why they lose or get a blank taskbar icon:
Window is taskbar-eligible.
Window becomes visible ? taskbar button created.
Window goes taskbar-ineligible.
Window becomes hidden ? since the window is not taskbar-eligible at this point, the taskbar ignores it.
Anyway, the basic idea is the following:
Get the handle of the window. (Parent window of the root in this case for tkinter related reasons)
get the current style in a hexadecimal code
alter the hexadecimal code to your need with a bitwise operation
apply the altered style to the window
I added the following code at the beginning of your script:
from ctypes import windll
GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080
def set_appwindow():
global hasstyle
if not hasstyle:
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
root.withdraw()
root.after(100, lambda:root.wm_deiconify())
hasstyle=True
This code at the end of your script:
hasstyle = False
root.update_idletasks()
root.withdraw()
set_appwindow()
and added the line set_appwindow() in def frame_map(event=None):. Als I had to implement another additional two lines in def minieme1_(event=None): to update the hasstylevariable.
So the overall implementation of this approach was possible to have your window ready and withdrawn for described reason. An additional variable to avoid a infinite loop while you alter the style of your window that is either True while it is shown or False while it is iconyfied, alongside with your overrideredirect method.
Full Code
from tkinter import*
from ctypes import windll
GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080
def set_appwindow():
global hasstyle
if not hasstyle:
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
root.withdraw()
root.after(100, lambda:root.wm_deiconify())
hasstyle=True
def move(e):
xwin = root.winfo_x()
ywin = root.winfo_y()
startx = e.x_root
starty = e.y_root
ywin -= starty
xwin -= startx
def move_(e):
root.geometry(f"+{e.x_root + xwin}+{e.y_root + ywin}")
startx = e.x_root
starty = e.y_root
frame.bind("<B1-Motion>",move_)
def minieme1_(event=None):
global hasstyle
root.update_idletasks()
root.overrideredirect(False)
root.state("iconic")
hasstyle = False
def frame_map(event=None):
root.overrideredirect(True)
root.update_idletasks()
set_appwindow()
root.state("normal")
def minimefunction(event=None):
global size
if size:
root.geometry(f"{screen_width}x{screen_height-40}+0+0")
minimsi.config(text=" \u2752 ")
size = False
else:
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
minimsi.config(text=" \u25a0 ")
size = True
def quitApp():
root.destroy()
def close_blink(event=None):
close_button.config(bg="red")
def close_blink1(event=None):
close_button.config(bg="gray19")
def minimsi_blink(event=None):
minimsi.config(bg="gray29")
def minimsi_blink1(event=None):
minimsi.config(bg="gray19")
def minimsi1_blink(event=None):
minimsi1.config(bg="gray29")
def minimsi1_blink1(event=None):
minimsi1.config(bg="gray19")
root = Tk()
size = True
app_width = 600
app_height = 500
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
print(screen_width,screen_height)
x = (screen_width/2) - (app_width/2)
y = (screen_height/2) - (app_height/2)
root.geometry(f"{app_width}x{app_height}+{int(x)}+{int(y)}")
root.overrideredirect(True)
frame = Frame(root,bg="gray29")
Label(frame,text="My App",font="Consolas 15",bg="gray29",fg="white").pack(side=LEFT,padx=10)
close_button = Button(frame,text=" X ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=quitApp)
close_button.pack(side=RIGHT)
minimsi = Button(frame,text=" \u25a0 ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minimefunction)
minimsi.pack(side=RIGHT)
minimsi1 = Button(frame,text=" - ",font="Consolas 15",bg="gray19",fg="white",relief=GROOVE,borderwidth=0,command=minieme1_)
minimsi1.pack(side=RIGHT)
frame.pack(fill=X)
yscroll = Scrollbar(orient=VERTICAL)
yscroll.pack(side=RIGHT,fill=Y)
editor = Text(font="Consolas 15",bg="gray19",fg="white",insertbackground="white",borderwidth=0,yscrollcommand=yscroll.set)
yscroll.config(command=editor.yview)
editor.pack(expand=True,fill=BOTH)
root.config(bg="gray19")
frame.bind("<Button-1>",move)
frame.bind("<B1-Motion>",move)
# minimsi1.bind("<Button-1>",minieme1_)
frame.bind("<Map>",frame_map)
close_button.bind("<Enter>",close_blink)
close_button.bind("<Leave>",close_blink1)
minimsi.bind("<Enter>",minimsi_blink)
minimsi.bind("<Leave>",minimsi_blink1)
minimsi1.bind("<Enter>",minimsi1_blink)
minimsi1.bind("<Leave>",minimsi1_blink1)
hasstyle = False
root.update_idletasks()
root.withdraw()
set_appwindow()
root.mainloop()
Tested with python 3.10 and windows 11
You can use a hidden root window to let the system window manager to show an icon in the taskbar, and make the custom window as a transient child window of the hidden root window in order to simulate those iconify and deiconify effect.
You need to bind <Button> event on the custom window so to bring it to the front when it is clicked. Also need to bind <FocusIn> event on the hidden root window to move the focus to the custom window instead.
Below is the required changes:
...
def minieme1_(event=None):
# iconify hidden root window will iconify the custom window as well
hidden_root.iconify()
...
def quitApp():
# destroy the hidden root window will destroy the custom window as well
hidden_root.destroy()
...
def on_focus(event):
# bring custom window to front
root.lift()
# create a hidden root window
hidden_root = Tk()
hidden_root.attributes('-alpha', 0)
hidden_root.title('My App')
# you can use hidden_root.iconbitmap(...) or hidden_root.iconphoto(...) to set the icon image
# use Toplevel instead of Tk for the custom window
root = Toplevel(hidden_root)
# make it a transient child window of hidden root window
root.transient(hidden_root)
root.bind('<Button>', on_focus)
hidden_root.bind('<FocusIn>', on_focus)
...
#frame.bind("<Map>",frame_map) # it is not necessary and frame_map() is removed as well
...
The short answer is: you can't do this purely with Tkinter. You can set the window's icon via self.iconbitmap(path_to_icon), but in order to have a unique taskbar icon, you'll need to compile your app into a Windows executable.
See here
Edit: Unrelated, but as a matter of practice it's best to avoid star imports, e.g. from tkinter import * - it's much better to use something like import tkinter as tk and then prefix your Tkinter objects with tk. to avoid namespace pollution!

Tkinter is there a way to resize the scroll bar in a canvas window

I have made most of this window already, and would prefer to not have to restart because of a hitch with a scrollbar not resizing properly. Problem being that the scrollbars appear way too small for the listboxes and I want them to span the whole height of each box respecitvely, but as of now they can only function if you spam the arrows as the actual scrolling bit can't move for lack of space. Any help would be appreciated, stuck on this for a while now. (Using python 3.8).
import tkinter as tk
from tkinter import *
setup = tk.Tk()
setup.title("Set Up Game")
setup.geometry("450x650")
setup.resizable(width=False, height=False)
select_Box = tk.Canvas(setup, width=450, height=496, bg="#cd3636")
select_Box.pack(padx=10)
listbox1 = Listbox(setup, width=33, height=30)
listbox1_win = select_Box.create_window(110,250, window=listbox1)
listbox2 = Listbox(setup, width=33, height=30)
listbox2_win = select_Box.create_window(320,250, window=listbox2)
scroll1 = Scrollbar(setup)
scroll1_win = select_Box.create_window(200,250, window=scroll1)
scroll2 = Scrollbar(setup)
scroll2_win = select_Box.create_window(410,250, window=scroll2)
listbox1.config(yscrollcommand = scroll1.set, selectmode=SINGLE)
scroll1.config(command = listbox1.yview)
listbox2.config(yscrollcommand = scroll2.set, selectmode=SINGLE)
scroll2.config(command = listbox2.yview)
nameArray = ["Bulbasaur", "Ivysaur", "Venasaur", "Charmander", "Charmelion", "Charazard", "Squirtle", "Wartortle", "Blastoise", "Lucario", "Garchomp", "Gengar", "Snorlax", "Reuniclus", "Joel","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder"]
for item in nameArray:
listbox1.insert(END, item)
setup.mainloop()
If you want to use Canvas.create_window to place all of your widgets, all you have to do is define the height of your scrollbar (you may need to play around with the numbers a little to get it to the right size).
So the edited snippet from your code will be:
scroll1 = Scrollbar(setup)
scroll1_win = select_Box.create_window(200,
250,
height=480, # this is all you're missing!
window=scroll1)

I Want to make a startup window in python Tkinter which should not have minimize, maximize and close button [duplicate]

I have a python program which opens a new windows to display some 'about' information. This window has its own close button, and I have made it non-resizeable. However, the buttons to maximize and minimize it are still there, and I want them gone.
I am using Tkinter, wrapping all the info to display in the Tk class.
The code so far is given below. I know its not pretty, and I plan on expanding the info making it into a class, but I want to get this problem sorted before moving along.
Anyone know how I can govern which of the default buttons are shown by the windows manager?
def showAbout(self):
if self.aboutOpen==0:
self.about=Tk()
self.about.title("About "+ self.programName)
Label(self.about,text="%s: Version 1.0" % self.programName ,foreground='blue').pack()
Label(self.about,text="By Vidar").pack()
self.contact=Label(self.about,text="Contact: adress#gmail.com",font=("Helvetica", 10))
self.contact.pack()
self.closeButton=Button(self.about, text="Close", command = lambda: self.showAbout())
self.closeButton.pack()
self.about.geometry("%dx%d+%d+%d" % (175,\
95,\
self.myParent.winfo_rootx()+self.myParent.winfo_width()/2-75,\
self.myParent.winfo_rooty()+self.myParent.winfo_height()/2-35))
self.about.resizable(0,0)
self.aboutOpen=1
self.about.protocol("WM_DELETE_WINDOW", lambda: self.showAbout())
self.closeButton.focus_force()
self.contact.bind('<Leave>', self.contactMouseOver)
self.contact.bind('<Enter>', self.contactMouseOver)
self.contact.bind('<Button-1>', self.mailAuthor)
else:
self.about.destroy()
self.aboutOpen=0
def contactMouseOver(self,event):
if event.type==str(7):
self.contact.config(font=("Helvetica", 10, 'underline'))
elif event.type==str(8):
self.contact.config(font=("Helvetica", 10))
def mailAuthor(self,event):
import webbrowser
webbrowser.open('mailto:adress#gmail.com',new=1)
In general, what decorations the WM (window manager) decides to display can not be easily dictated by a toolkit like Tkinter. So let me summarize what I know plus what I found:
import Tkinter as tk
root= tk.Tk()
root.title("wm min/max")
# this removes the maximize button
root.resizable(0,0)
# # if on MS Windows, this might do the trick,
# # but I wouldn't know:
# root.attributes(toolwindow=1)
# # for no window manager decorations at all:
# root.overrideredirect(1)
# # useful for something like a splash screen
root.mainloop()
There is also the possibility that, for a Toplevel window other than the root one, you can do:
toplevel.transient(1)
and this will remove the min/max buttons, but it also depends on the window manager. From what I read, the MS Windows WM does remove them.
from tkinter import *
qw=Tk()
qw.resizable(0,0) #will disable max/min tab of window
qw.mainloop()
from tkinter import *
qw=Tk()
qw.overrideredirect(1) # will remove the top badge of window
qw.mainloop()
here are the two ways to disable maximize and minimize option in tkinter
remember the code for button shown in image is not in example as this is solution regarding how to make max/min tab nonfunctional or how to remove
Windows
For windows, you can use -toolwindow attribute like that:
root.attributes('-toolwindow', True)
So if you want complete code, it's that
from tkinter import *
from tkinter import ttk
root = Tk()
root.attributes('-toolwindow', True)
root.mainloop()
Other window.attributes attributes:
-alpha
-transparentcolor
-disabled
-fullscreen
-toolwindow
-topmost
Important note this is only working with Windows. Not MacOS
Mac
With mac you can use overredirect attribute and a "x" button to close the window and this will do the job. :D like that:
from tkinter import *
from tkinter import ttk
window = Tk()
window.overredirect(True)
Button(window, text="x", command=window.destroy).pack()
window.mainloop()
Inspired by https://www.delftstack.com/howto/python-tkinter/how-to-create-full-screen-window-in-tkinter/
For me, it's working, i have a windows 7.
Comment me if i have a error.
I merged answers from #demyaN and the others, and the following is a way to get the job done.
import ctypes as ct
from tkinter import *
def setWinStyle(root):
set_window_pos = ct.windll.user32.SetWindowPos
set_window_long = ct.windll.user32.SetWindowLongPtrW
get_window_long = ct.windll.user32.GetWindowLongPtrW
get_parent = ct.windll.user32.GetParent
# Identifiers
gwl_style = -16
ws_minimizebox = 131072
ws_maximizebox = 65536
swp_nozorder = 4
swp_nomove = 2
swp_nosize = 1
swp_framechanged = 32
hwnd = get_parent(root.winfo_id())
old_style = get_window_long(hwnd, gwl_style) # Get the style
new_style = old_style & ~ ws_maximizebox & ~ ws_minimizebox # New style, without max/min buttons
set_window_long(hwnd, gwl_style, new_style) # Apply the new style
set_window_pos(hwnd, 0, 0, 0, 0, 0, swp_nomove | swp_nosize | swp_nozorder | swp_framechanged) # Updates
window = Tk()
Button(window, text="button").pack() # add your widgets here.
window.after(10, lambda: setWinStyle(window)) #call to change style after the mainloop started. Directly call setWinStyle will not work.
window.mainloop()
By the way, using window.attributes('-toolwindow', True) will remove the minimize and maximize boxes, but it will make the app not display in the taskbar, which is a problem for me.
Removing minimize/maximize buttons using ctypes
import ctypes as ct
set_window_pos = ct.windll.user32.SetWindowPos
set_window_long = ct.windll.user32.SetWindowLongPtrW
get_window_long = ct.windll.user32.GetWindowLongPtrW
get_parent = ct.windll.user32.GetParent
# Identifiers
gwl_style = -16
ws_minimizebox = 131072
ws_maximizebox = 65536
swp_nozorder = 4
swp_nomove = 2
swp_nosize = 1
swp_framechanged = 32
hwnd = get_parent(settings_panel.winfo_id())
# Get the style
old_style = get_window_long(hwnd, gwl_style)
# New style, without max/min buttons
new_style = old_style & ~ ws_maximizebox & ~ ws_minimizebox
# Apply the new style
set_window_long(hwnd, gwl_style, new_style)
# Updates
set_window_pos(hwnd, 0, 0, 0, 0, 0, swp_nomove | swp_nosize | swp_nozorder | swp_framechanged)

Tkinter Toplevel() positioning without static geometry

Im using Toplevel() for popup windows and I want the popup to be displayed to the right of the mouse when it comes up. I found how to do this but only by specifying the geometry of the window. How can I control where the window comes up without specifying the size. I want the window to be the size it needs to be for whatever data is is going to display.
This is what im using right now:
helpwindow = Toplevel()
helpwindow.overrideredirect(1)
helpwindow.geometry("662x390+{0}+{1}".format(event.x_root - 1, event.y_root - 12))
How can I put only the format settings in the window geometry? Or is their a better way?
Use "+{}+{}" without size
helpwindow.geometry("+{}+{}".format(event.x_root - 1, event.y_root - 12))
ie. moving window :)
import tkinter as tk
def move():
global pos_x
helpwindow.geometry("+{}+200".format(pos_x))
pos_x += 10
root.after(100, move)
root = tk.Tk()
pos_x = 0
helpwindow = tk.Toplevel()
move()
root.mainloop()

Tkinter notebook - Too many tabs for window width

I am having a problem with my first tkinter (Python 3) notebook app.
The canvas on which the data is displayed only needs to be 775px wide, by 480px high. This is all very well until the number of tabs makes the window wider than that. All the data is placed on one side and the other is a sea of emptyness. I have tried to make the notebook widget scrollable but I cannot get it to work.
Any advice would be greatly received.
#!/usr/bin/python
# Try to work with older version of Python
from __future__ import print_function
import sys
if sys.version_info.major < 3:
import Tkinter as tk
import Tkinter.ttk as ttk
else:
import tkinter as tk
import tkinter.ttk as ttk
#============================================================================
# MAIN CLASS
class Main(tk.Frame):
""" Main processing
"""
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.root_f = tk.Frame(self.root)
self.width = 700
self.height = 300
# Create a canvas and scroll bar so the notebook can be scrolled
self.nb_canvas = tk.Canvas(self.root_f, width=self.width, height=self.height)
self.nb_scrollbar = tk.Scrollbar(self.root_f, orient='horizontal')
# Configure the canvas and scrollbar to each other
self.nb_canvas.config(yscrollcommand=self.nb_scrollbar.set,
scrollregion=self.nb_canvas.bbox('all'))
self.nb_scrollbar.config(command=self.nb_canvas.xview)
# Create the frame for the canvas window, and place
self.nb_canvas_window = tk.Frame(self.nb_canvas, width=self.width, height=self.height)
self.nb_canvas.create_window(0, 0, window=self.nb_canvas_window)
# Put the whole notebook in the canvas window
self.nb = ttk.Notebook(self.nb_canvas_window)
self.root_f.grid()
self.nb_canvas.grid()
self.nb_canvas_window.grid()
self.nb.grid(row=0, column=0)
self.nb_scrollbar.grid(row=1, column=0, sticky='we')
self.nb.enable_traversal()
for count in range(20):
self.text = 'Lots of text for a wide Tab ' + str(count)
self.tab = tk.Frame(self.nb)
self.nb.add(self.tab, text=self.text)
# Create the canvas and scroll bar for the tab contents
self.tab_canvas = tk.Canvas(self.tab, width=self.width, height=self.height)
self.tab_scrollbar = tk.Scrollbar(self.tab, orient='vertical')
# Convigure the two together
self.tab_canvas.config(xscrollcommand=self.tab_scrollbar.set,
scrollregion=self.tab_canvas.bbox('all'))
self.tab_scrollbar.config(command=self.tab_canvas.yview)
# Create the frame for the canvas window
self.tab_canvas_window = tk.Frame(self.tab_canvas)
self.tab_canvas.create_window(0, 0, window=self.tab_canvas_window)
# Grid the content and scrollbar
self.tab_canvas.grid(row=1, column=0)
self.tab_canvas_window.grid()
self.tab_scrollbar.grid(row=1, column=1, sticky='ns')
# Put stuff in the tab
for count in range(20):
self.text = 'Line ' + str(count)
self.line = tk.Label(self.tab_canvas_window, text=self.text)
self.line.grid(row=count, column=0)
self.root.geometry('{}x{}+{}+{}'.format(self.width, self.height, 100, 100))
return
# MAIN (MAIN) =======================================================
def main():
""" Run the app
"""
# # Create the screen instance and name it
root = tk.Tk()
# # This wll control the running of the app.
app = Main(root)
# # Run the mainloop() method of the screen object root.
root.mainloop()
root.quit()
# MAIN (STARTUP) ====================================================
# This next line runs the app as a standalone app
if __name__ == '__main__':
# Run the function name main()
main()
OK, so I think I understand now. The tabs are inside the notebook, and inseperable from the notebook. As such, the notebook will always be as wide as the frames within it. To get the effect I wanted I would need put a canvas into the notebook, and then add the tabs the the canvas. And that is not allowed. So back to the drawing board!
If the tabs are of 'constant' width and you know how many will fit the desired (fixed?)size of the window, you could create a "scrolling tabs" widget by hiding the ones that don't fit your width. Create two buttons, left and right that for example hides the one to the right and shows the next hidden one to the left.
If there a way to figure out the width of a tab (fontsize in the label, padding etc?) it could be done more 'dynamic'.
I would recommend combining the solutions from here: Is there a way to add close buttons to tabs in tkinter.ttk.Notebook? (to be able to close a tab) and here: https://github.com/muhammeteminturgut/ttkScrollableNotebook to use buttons instead of a scroll-bar to handle the width issue.
Two changes to get it to work are to load the "notebookTab" variable as the CustomNotebook and to put the closing icon on the left side by switching the order of the innermost children of style.layout in the first answer. This produces a slidable and closeable custom notebook type.

Categories

Resources