modifying notebook tabs and styles in general
From
aapost@21:1/5 to
All on Mon Dec 26 22:21:41 2022
Curious if there might be any advanced creative ways to dig a little
deeper and improve on what I have so far without modifying tcl/tk itself.
I am implementing a close button on Notebook tabs (only found one
example elsewhere that helped a bit) and am pretty close to what I want
(to a workable extent..) but not quite fully how I want it.
I am using python/tkinter 3.11.1, tcl/tk 8.6.13, debian testing for my
main project -- BUT I rewrote the example in tcl as well, so both
versions are below. Asside from a few language differences due to my not knowing tcl (variable scope/naming/type stuff), it is functionally the
same. (I may need to address memory clean up, but haven't
researched/focussed on that yet)
A few comments before the code.
ttk::style element create seems interesting at first, but not as useful
or interesting as it could be once you dig in...
You can create a predefined image with it, that has a padding/border,
and a unique element name which you can grab later in code, but it can't inherit any options as far as I can tell from other elements.. which
makes it static and not useful for what I want, I can't dynamically use
it or not use it like I can a label image with an image option.
You can also duplicate an element and create a unique name using "from",
but it can't inherit from multiple things (to make a compound like a
label text+image), and you can't take away element options from it to
make.. a more basic widget.. so the new creation is just a copy with a
new element name, and all it's options overlap with the original.. so a .padding2 is the same thing as a .padding when modifying a layout, and
the setting of those are hard coded in the widget design.
The "tab" on notebook and it's properties are not very accessible in
code. with notebook 'add', you add a frame or widget for the main area,
along with label-like options for the tab itself, but it's all a bit
obfuscated away from the tangible object. (it would be nice if it had
more direct
customization, maybe something similar to -labelwidget from LabelFrame,
though I haven't dug in to that widget yet)
So as far as what I have been able to do.
I can tell the tabs have the same options as a label when you do a
notebook add I can see when looking at the layout for TNotebook.Tab, it
has a .label nested at the end.
I can look at the element options for label, and look at what other
elements are available, and see that it seems to be the combination of a
.text and a .image element (different than the 'image' created via
element create etype image, because this premade .image HAS options and
is the element (i believe) that a label uses)
So I can swap out
('TNotebook.label', {'side': 'top','sticky': ''}),
for:
('TNotebook.text', {'side': 'top','sticky': ''}),
('TNotebook.image', {'side': 'top','sticky': ''}),
and the label options given to the notebook 'add' flow through to each
of those corresponding elements options as if they were a label.
You lose the 'compound' option. but that doesn't matter in my case.
What I would like, is some way to add a little bit of invisible padding
between the .text and the .image (preferably dynamic), something like a
2nd padding set to {2 0 0 0}, but I assume that probably isn't possible.
I have tried a number of things, .border is already used/set somewhere
outside the TNotebook.Tab stack, and I don't really see anything else
that I might be able to manupulate that I am not doing already. (It also
leave behind static empty padding containers when the image isn't used.)
Anyway, below is the example, first tcl, then python:
####################
#TCL
####################
package require Tk
global _clicked_tab
wm geometry . 400x400
proc on_x_press {widget x y} {
global _clicked_tab
set elem [$widget identify $x $y]
# or custom element name
if {$elem == "image"} {
set index [$widget index @$x,$y]
$widget tab $index -image ::img::x1_img
$widget state pressed
set _clicked_tab $index
return break
}
}
proc on_x_release {widget x y} {
global _clicked_tab
if {![$widget instate pressed]} {
return
}
set elem [$widget identify $x $y]
set index [$widget index @$x,$y]
#or custom element name
if {$elem != "image" || $index == "" || $index != $_clicked_tab} {
$widget state !pressed
$widget tab $_clicked_tab -image { ::img::x_img active ::img::x2_img }
set _clicked_tab None
return
}
if {$_clicked_tab == $index} {
$widget forget $index
event generate $widget <<NotebookTabClosed>>
}
$widget state !pressed
set _clicked_tab None
}
image create photo ::img::x_img -format GIF -data {
R0lGODdhEAAQAIAAAAAAANnZ2SwAAAAAEAAQAAACJIyPaaDK+1wMoMpJ7akYPt54
YCiS20hy6OU165p1bwdt5nIrBQA7
}
image create photo ::img::x1_img -format GIF -data {
R0lGODdhEAAQAKEAAAAAAP8AANnZ2QAAACwAAAAAEAAQAAACKZSPaaDKFkITEUWZ
bq5HK+81VziNk0WKI6ZiKcTBrPzF1IwezynsPFIAADs=
}
image create photo ::img::x2_img -format GIF -data {
R0lGODdhEAAQAKEAAAAAADiGLtnZ2QAAACwAAAAAEAAQAAACKZSPaaDKFkITEUWZ
bq5HK+81VziNk0WKI6ZiKcTBrPzF1IwezynsPFIAADs=
}
ttk::style layout TNbimgtabs {TNbimgtabs.client -sticky nswe}
ttk::style layout TNbimgtabs.Tab {
TNbimgtabs.tab -sticky nswe -children {
TNbimgtabs.padding -side top -sticky nswe -children {
TNbimgtabs.focus -side top -sticky nswe -children {
TNbimgtabs.text -side left sticky
TNbimgtabs.image -side left sticky
}
}
}
}
ttk::style configure TNbimgtabs.Tab -padding {4 2} -background #c3c3c3 ttk::style map TNbimgtabs.Tab -background { selected #d9d9d9 }
ttk::notebook .nb -style TNbimgtabs
pack .nb -expand true -fill both
for {set i 0} {$i<4} {incr i} {
.nb add [ttk::frame .nb.f$i] \
-text "Tab $i" \
-image { ::img::x_img active ::img::x2_img }
#-compound right
}
set _clicked_tab None
bind .nb <ButtonPress-1> {on_x_press %W %x %y}
bind .nb <ButtonRelease-1> {on_x_release %W %x %y}
####################
#PYTHON
####################
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
root.geometry("400x400")
#these may need to be tied to a widget or root Tk() class,
#they are self.x_img in my code, as I was having issues
#getting them to show up if they were defined by themselves
x_img = tk.PhotoImage(
name="x_img", master=root,
data="R0lGODdhEAAQAIAAAAAAANnZ2SwAAAAAEAAQAAACJIyPaaDK+1wMoMpJ7akYPt54YCiS20hy6OU165p1bwdt5nIrBQA7"
)
x1_img = tk.PhotoImage(
name="x1_img", master=root,
data="R0lGODdhEAAQAKEAAAAAAP8AANnZ2QAAACwAAAAAEAAQAAACKZSPaaDKFkITEUWZbq5HK+81VziNk0WKI6ZiKcTBrPzF1IwezynsPFIAADs="
)
x2_img = tk.PhotoImage(
name="x2_img", master=root,
data="R0lGODdhEAAQAKEAAAAAADiGLtnZ2QAAACwAAAAAEAAQAAACKZSPaaDKFkITEUWZbq5HK+81VziNk0WKI6ZiKcTBrPzF1IwezynsPFIAADs="
)
style = ttk.Style()
#these commented things don't work
#style.element_create("otherimg", "image", "x_img",
# ("active", "!disabled", "x_img"),
# border=6, sticky='') #style.element_create("imagewpadding", "from", "default", "image") #style.element_create("otherpadding", "from", "default", "padding") #style.layout("ImageWPadding", [
# ("ImageWPadding.imagewpadding", {
# 'side': 'left',
# 'sticky': '',
# 'children': [
# ('TNbimgtabs.imagewpadding',
{'side': 'left', 'sticky': ''}),
# ]
# })
# ])
style.layout("TNbimgtabs", [("TNbimgtabs.client", {"sticky": "nswe"})]) style.layout("TNbimgtabs.Tab", [
('TNbimgtabs.tab', {
'sticky': 'nswe',
'children': [
('TNbimgtabs.padding', {
'side': 'top',
'sticky': 'nswe',
'children': [
('TNbimgtabs.focus', {
'side': 'top',
'sticky': 'nswe',
'children': [
#('TNbimgtabs.label',
{'side': 'top','sticky': ''}),
('TNbimgtabs.text',
{'side': 'left', 'sticky': ''}),
('TNbimgtabs.image',
{'side': 'left', 'sticky': ''}),
#('TNbimgtabs.border', {
# 'side': 'left',
# 'sticky': '',
# 'children': [
#
('TNbimgtabs.imagewpadding', {'side': 'left', 'sticky': ''}),
# ]
#})
]
})
]
})
]
})
]
)
#these are the defaults that TNotebook have on my debian testing /
python 3.11 system
#they don't carry over when defining a new layout above, and you have to
figure out
#what they are by running .lookup() and digging in source code,
-tabmargins shown in
#altTheme.tcl does not appear implemented in python style.configure('TNbimgtabs.Tab', padding=(4, 2), background="#c3c3c3") style.map('TNbimgtabs.Tab', background=[('selected', '#d9d9d9')])
def on_x_press(widget, event):
element = widget.identify(event.x, event.y)
# or custom element name
if "image" in element:
index = widget.index("@%d,%d" % (event.x, event.y))
widget.tab(index, image="x1_img")
# self.tab(index, padding=50)
widget.state(['pressed'])
widget._clicked_tab = index
return "break"
def on_x_release(widget, event):
if not widget.instate(['pressed']):
return
element = widget.identify(event.x, event.y)
try:
index = widget.index("@%d,%d" % (event.x, event.y))
except:
index = None
#print("no index")
#or custom element name
if ("image" not in element) or index == None or index != widget._clicked_tab:
widget.state(['!pressed'])
widget.tab(widget._clicked_tab, image=["x_img", ("active",
"!disabled"), "x2_img", ])
widget._clicked_tab = None
return
if widget._clicked_tab == index:
widget.forget(index)
widget.event_generate("<<NotebookTabClosed>>")
widget.state(["!pressed"])
widget._clicked_tab = None
nb = ttk.Notebook(style='TNbimgtabs')
nb.pack(expand=True, fill=tk.BOTH)
nb.tabs = []
for x in range(4):
nb.tabs.append(ttk.Frame(master=nb))
nb.add(nb.tabs[-1], text="Tab " + str(x), image=["x_img", ("active", "!disabled"), "x2_img",])
nb.bind("<ButtonPress-1>", lambda event, widget=nb: on_x_press(widget,
event))
nb.bind("<ButtonRelease-1>", lambda event, widget=nb:
on_x_release(widget, event))
root.mainloop()
--- SoupGate-Win32 v1.05
* Origin: fsxNet Usenet Gateway (21:1/5)