Saturday, January 23, 2016

python tkinter label scrollbar 1

For my project I wanted to have an event log window showing to which I could write messages at the bottom and have them scroll up as more messages were added, with the top ones scrolling off the top of the window. I wanted to have a scrollbar on the side that would allow the user to access the earlier messages. In my searching, I found a few hints on how to implement a scrollbar, but initially couldn't make it work, so the first design that I got working was to instead have a fixed size Label widget to which new lines got added at the bottom, with the old lines simply scrolling out of view at the top.

There is some reason why a "Text" widget won't work for this application, I can't remember exactly why but I think that it might be an inability to dynamically update it. The "Label" widget allows you to use a StringVar to define its contents, and then update the StringVar any time you want to refresh the Label. As with most things in Python, there is no limit to the size of the StringVar, so I can be lazy and just keep adding and adding lines to the StringVar without having to worry about trimming text from the beginning; the fixed size of the label and anchoring the text to the bottom of the label then takes care of the scrolling for me.

Below is my actual design, in which I simply defined a fixed geometry for my Label widget, set wordwrap so that long horizontal lines get wrapped, then I defined the anchor for the text to be in the bottom left corner. Then I add lines to StringVar for the Label with '\n' after each new line I add. The Label redraws itself to show the new value of the StringVar, and any lines that don't fit within the fixed geometry are drawn but are off the top of the visible area so it gives the illusion of lines scrolling off the top. If the geometry of the widget weren't fixed, it would resize to show all of the lines. In this example code f is the main frame of my GUI; I pack the label widget into a new frame f5 which is in turn packed under some other objects that I'd defined earlier in the frame up to that point:

f5 = Tkinter.Frame(f)
self.stattext = Tkinter.StringVar()
self.stattext.set("")
self.status = Tkinter.Label(f5,textvar=self.stattext, justify="left",anchor="sw",width=80,height=10, wraplength=640)
self.status.pack(side="left", padx=10)
f5.pack(fill="x")

Here are some links with examples that look like what I did:
This is one where somebody is updating a StringVar to make a Label widget update. It doesn't have the fixed geometry like I used. The OP for this forum post has a minor problem urelated to the use of the Label. http://stackoverflow.com/questions/1918005/making-python-tkinter-label-widget-update

With regards to having a Label widget with a scrollbar, it turns out that scrollbars are not a native property of Labels, but since Labels by default resize themselves the answer is to put one in a "Canvas" which is a type of frame that you can define all kinds of parameters for such as that it doesn't resize but it can have scrollbars. Initially I was not able to get this to work, so I went with the other solution above. Here are some links about using a Canvas:

http://stackoverflow.com/questions/7113937/how-do-you-create-a-labelframe-with-a-scrollbar-in-tkinter

http://stackoverflow.com/questions/16188420/python-tkinter-scrollbar-for-frame