Resizeable Tk windows done right
There is something wrong with the way I assemble my Tk windows (with R tcltk and tcltk2, under Win XP)
library(tcltk)
library(tcltk2)
开发者_Python百科expandTk <- function() {
root <- tktoplevel()
# textbox with scroll bars
textbox <- tk2frame(root)
scr <- tkscrollbar(textbox, repeatinterval=5, command=function(...) tkyview(txt,...))
txt <- tktext(textbox, bg="white", font="courier", wrap="word", yscrollcommand=function(...)tkset(scr,...))
tkpack(txt, side="left", fill="both", expand=TRUE)
tkpack(scr, side="right", fill="y")
tkmark.set(txt,"insert","0.0")
tkpack(textbox, fill="both", expand=TRUE)
# status bar and size grip
statusText <- tclVar("")
f <- tk2frame(root, relief="sunken")
l <- tk2label(f, textvariable=statusText)
tkpack(l, side="left", pady=2, padx=5, expand=0, fill="x")
tkpack(f, side="left", expand=1, fill="x", anchor="s")
sg <- ttksizegrip(root)
tkpack(sg, side="left", expand=0, anchor="se")
}
The window looks fine, but as soon as I resize it (ie make it smaller), the scrollbar and the statusbar disappears. I am quite sure that this is a user error, I have seen other Tk apps which resize properly, but I can not figure out which option I should use...
Any hint appreciated, Karsten
That's standard behavior for the Tk pack
geometry manager. Here's a relevant section of the pack
man page:
If the cavity should become too small to meet the needs of a slave then the slave will be given whatever space is left in the cavity. If the cavity shrinks to zero size, then all remaining slaves on the packing list will be unmapped from the screen until the master window becomes large enough to hold them again.
So if you shrink the overall window to be smaller than the space requested for the text widget, you leave no space for the other widgets, and they get unmapped.
The best solution is to use the grid
geometry manager, instead of pack
. Generally speaking, I find grid
to be much easier to use and capable than pack
anyway, although your mileage may vary. In particular, it eliminates the need for many superfluous frame widgets, which can simplify your code a lot.
I think you can use something like this:
expandTk <- function() {
root <- tktoplevel()
# textbox with scroll bars
textbox <- tk2frame(root)
txt <- tktext(textbox, bg="white", font="courier", wrap="word", yscrollcommand=function(...)tkset(scr,...))
scr <- tkscrollbar(textbox, repeatinterval=5, command=function(...) tkyview(txt,...))
tkmark.set(txt,"insert","0.0")
# Set up the geometry for the stuff inside the "textbox" frame.
# The text and scrollbar widgets live on the same row.
tkgrid(txt, scr)
# The text widget should stick to all four sides of its parcel.
tkgrid.configure(txt, sticky="nsew")
# The scrollbar should stick to the top and bottom of its parcel, it need not stick to the
# left and right.
tkgrid.configure(scr, sticky="ns")
# When the window is resized, we want surplus space to be allocated to the text widget,
# which is in the top left corner of this frame.
tkgrid.columnconfigure(textbox,0,weight=1)
tkgrid.rowconfigure(textbox,0,weight=1)
# status bar and size grip
statusText <- tclVar("")
l <- tk2label(root, textvariable=statusText,relief="sunken")
sg <- ttksizegrip(root)
# Set up the geometry for the stuff inside the "root" window.
# First row is just the textbox frame...
tkgrid(textbox)
# Second row is the status label and the resize gadget
tkgrid(l, sg)
# The textbox widget should span 2 colums, and stick to all four sides of its parcel.
tkgrid.configure(textbox,columnspan=2,sticky="nsew")
# The status label should stick to all four sides of its parcel too
tkgrid.configure(l,sticky="nsew")
# The resize gadget should only stick to the bottom right of its parcel
tkgrid.configure(sg,sticky="se")
# When the window is resized, we want surplus space to go to the textbox frame (and from there
# to the text widget itself, which it will do thanks to the grid weights we set up above). The
# textbox frame is in the top left corner of its parent window.
tkgrid.columnconfigure(root,0,weight=1)
tkgrid.rowconfigure(root,0,weight=1)
}
There's some more information about using the grid
geometry manager from R here.
If you're insistent on packing the widgets, you should be aware that if there isn't enough space to give all widgets the room they asked for, space is given preferentially to the first widgets packed (within a particular container). Put the statusbar in first, then the scrollbars, and only then the main widget. (You may need to alter which side you're packing particular widgets on to make it all work right.) Also, if it is getting too complicated, remember that you can pack inside frames packed inside frames; that gives you lots of flexibility.
But that's the point when using the grid geometry manager just makes sense. It gives you a lot more fine control when you're going for that last 10% of the look of your app, and needs less nesting of widgets to achieve it.
Definitely grid is best, as Eric points out, but if you really want to use pack then resizing the expanding txt widget, like below, will get you what you are looking for. There are some size heuristics that could be improved.
Add this to the end of your code:
widthOfChar <- ceiling(as.numeric(tclvalue(tcl("font","measure","TkTextFont","0123456789")))/10) + 2
tkbind(root, "<Configure>", function(W) {
w.width <- as.integer(tkwinfo("width",W))
txt.width <- w.width - 15L
tkconfigure(txt, width=floor(txt.width/widthOfChar))
})
精彩评论