Showing posts with label Tkinter. Show all posts
Showing posts with label Tkinter. Show all posts

Wednesday, March 9, 2016

Tkinter make a widget invisible or visible

The "disable" function greys out a check button and even a label, but is it possible to temporarily make those disappear off a screeen and reappear? So far what I've found seems to indicate that there are a couple of options. There seem to be some utilities for undoing packing in such a way that it's possible to easily redo it. It might also be possible to play with front and back layering to cover and uncover items on a GUI. Here are some links:

This is a nice discussion of these two methods: http://stackoverflow.com/questions/3819354/in-tkinter-is-there-any-way-to-make-a-widget-not-visible

A terse version of the same answer (it points to the first link as already answered): http://stackoverflow.com/questions/10267465/showing-and-hiding-widgets

Only slightly related, here's a cautionary tale about a guy who made a mess of his frame managers and how it was fixed: http://stackoverflow.com/questions/30061100/tkinter-self-grid-making-widgets-invisible

Monday, February 22, 2016

Tkinter check boxes

Tkinter doesn't have widgets that look like switches like Labview or anything, but apparently most OS's can be counted on to have check boxes so that's a thing in Tkinter. Either I'm finally getting used to this or it's' pretty easy to find useful examples on how to define checkboxs and how to grey them out:

Basic man page. In the examples the author seems to like the grid manager, but pack works just fine: http://www.python-course.eu/tkinter_checkboxes.php

A reminder to use an IntVar to return the status of the checkbox: http://stackoverflow.com/questions/16285056/get-the-input-from-a-checkbox-in-python-tkinter

A compact man page with nice lists of parameters and methods, including disable. http://www.tutorialspoint.com/python/tk_checkbutton.htm

A similar page for buttons showing how you can use the same settings to grey them out too: http://www.tutorialspoint.com/python/tk_button.htm



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

Friday, January 22, 2016

python tkinter how to pop up a window from inside a class

It took a little bit of searching and pondering of the info that I had found to accomplish one of the most important functions in my GUI after I recoded it to properly use the Tkinter mainloop, which is how to pop up custom dialog windows when something happened on the main GUI. My GUI is all under a single class, which is a good way of doing it but not much like the super simple examples that are often found online for creating dialog windows. I needed dialogs that were more customized than the built in Message or OK dialogs. The answer was for each dialog launched from the main GUI to create an instance of the Tkinter.Toplevel object and have that be the root of the new dialog window. This essentially creates a new window frame, it's like the Tkinter.Tk object that you use to create your GUI at the initialization of the class. You then pack data entry and/or display widgets into that Toplevel object. All of the objects you put in this frame are still accessible from the main class as long as you store the pointers to them in the class; essentially this new window is just an extension of the original window. Declare the callbacks for each of the widgets in the dialog as part of the class; you can allow the callbacks to destroy the Toplevel object by its pointer in order to perform the function of dismissing the dialog. There's a magic function of the Toplevel object (.transient()) which allows you to define that it appears on top of the original GUI window, and then you can place it using geometry commands like you would your main window. The only trick is to keep all the coding event-driven; for a complex program that means employing the state machine type of coding where you build the dialog in one state and then let user interaction with the dialog widgets cause advancement to a new state.

Here's my example of how I did it; not necessarily the best way but fairly functional:

import Tkinter,tkMessageBox,ttk
class mygui(Tkinter.Tk):

def __init__(self, *args, **kwargs):
Tkinter.Tk.__init__(self, *args, **kwargs)

self.title('My GUI Main Window')

# Making a fancy frame here with a blue border just for fun
# Bottom layer is a frame with blue fill
frm_0 = Tkinter.Frame(self, bg="blue")

# This is the main frame that will be on top of the blue frame
f = Tkinter.Frame(frm_0)

# Putting some text in the frame just to have a frame.
# Actual GUI would have more stuff
self.mylabel = Tkinter.Label(f,text="This is a label ",justify="left")
self.mylabel.pack(side="left",padx=10,pady=10)

# Place the main frame inside the blue frame with some padding
# so that the blue of the lower layer shows all around it
f.pack(fill="both",padx=5,pady=5)
frm_0.pack()

# Place the bottom frame where I want it (in the center)
frm_0.place(relx=0.5,rely=0.5,anchor="center")

# Make this example gui the size of the packed controls,
# in the middle of the screen
self.update_idletasks()
width = frm_0.winfo_width()
height = frm_0.winfo_height()
x = self.winfo_screenwidth() // 2 - width // 2
y = self.winfo_screenheight() // 2 - height // 2
self.geometry('{}x{}+{}+{}'.format(width, height, x, y))

# Initialize the state machine to the first state
self.state = 0

# Initialize other variables in this class
self.mytext = ''
self.text_accepted = False
self.abort_selected = False

# Define all the widget callback functions for the class
def accept(self, event=None):
self.mytext = self.e.get()
self.text_accepted = True
self.top.destroy()

def close_top(self):
self.mytext = ''
self.abort_selected = True
self.top.destroy()

# Main executive loop. This is where the part of the program that
# actually does things would go
def executive(self):

if (self.state == 0):
# First state, for doing stuff before popping up the dialog
print "Doing stuff in first state."

# Set next state, schedule the next call of exeutive() and exit
# In this example there is only one state before dialog; could
# be any number of states before the dialog
self.state = 1
self.executive()

elif (self.state == 1):
# Draw dialog popup
self.top = Tkinter.Toplevel()
msg = 'Enter text and press accept'
self.top.title(msg)

frm1 = Tkinter.Frame(self.top)
self.e = ttk.Entry(frm1)
self.e.config(width=(len(msg)+20))
self.e.insert(0,"default text")
self.e.pack(side="left",padx=5,pady=5)
b = Tkinter.Button(frm1, text="Accept", command=self.accept)
b.pack(side='left',padx=5,pady=5)
frm1.pack()

self.top.protocol("WM_DELETE_WINDOW", self.close_top)

# Make sure this dialog stays on top of the root console
self.top.transient(self)

# Make this dialog positioned in the center of the screen
self.update_idletasks()
win_width = self.top.winfo_width()
win_height = self.top.winfo_height()
x = self.winfo_screenwidth() // 2 - win_width // 2
y = self.winfo_screenheight() // 2 - win_height // 2
self.top.geometry('{}x{}+{}+{}'.format(win_width, win_height, x, y))

# set state to next state and exit
self.state = 2
self.executive()

elif (self.state == 2):
# waiting for the accept button to be pushed.
if self.text_accepted:
self.state = 3
self.executive()
elif self.abort_selected:
self.state = 4
self.executive()
else:
self.after(100,self.executive)

elif (self.state == 3):
# Do something with the input (boring example).
print self.mytext
self.state = 4
self.executive()

elif (self.state == 4):
# Exit state. Call the built-in .quit() function to exit the GUI
# Note that you can still call a built-in MessageBox widget any time
tkMessageBox.showinfo(title="Example complete",message="Example complete. Click OK to

exit.")
self.quit()

# Note that this "start" function is basically redundant, however having
# a function with this name helps readability.
def start(self):
self.state = 0;
self.executive()

if __name__=='__main__':

# Main code for example

app = mygui()
app.start()
app.mainloop()
app.destroy()

BeagleBone Debian Install Tkinter

This is the procedure that I've used twice now to update a fresh Debian distribution with Tkinter. The distro comes with Python 2.7 but not Tkinter.

Step 1: Put the BeagleBone on my home network with the CAT5 cable from the router. It gets a DHCP address on the network automatically from the router.

Step 2: Go to root user.
sudo su

Step 3: Update apt-get, which synchronizes what packages my distro has and doesn't have and establishes locations to download from.
apt-get update

Step 4: Download and install Tkinter. Despite what I read online about other users' issues with this step, it seemed to work fine for me.
apt-get install python-tk

Some related links:

The basic instructions, which include the above steps http://tkinter.unpythonic.net/wiki/How_to_install_Tkinter

About how to use apt-cache search which while not specifically needed to do this installation is generally helpful. Also, aptitude is mentioned: http://askubuntu.com/questions/160897/how-do-i-search-for-available-packages-from-the-command-line

More about apt-cache search, and also some more about how to use aptitude: http://www.cyberciti.biz/faq/searching-for-packages-in-debian-ubuntu-aptitude/





Wednesday, January 13, 2016

Python Tkinter progress bar

Progress bars are super easy in Tkinter, and there are two flavors. The regular kind is a bar that expands from left to right based on setting the object's "value" with respect to its "maxvalue". That is called a "determinate" progress bar. There is also an "indeterminate" progress bar that has a section that bounces back and forth from end to end to show general aliveness of a task. With an indeterminate progress bar, you can just call it's start() and stop() functions, or use a more direct method of showing aliveness by inserting step() commands in the code (.step accepts a percent value from 0 to 100). Some links:

http://stackoverflow.com/questions/7310511/how-to-create-downloading-progress-bar-in-ttk

https://gist.github.com/livibetter/6850443

The default Windows progress bar was pretty nasty looking, especially the indeterminate one which had a very thin indicator. It turns out that the way to change this is to define a ttk progressbar "style," which allows the programmer to chose from a handful of named styles, and then set colors for components. There's a command that you can do in a python console to get a list of available styles on whatever OS you're working in. Some are available on most platforms, others might be specific to whatever platform you're on. Some commentators to some forum threads that I checked out seemed to love the "clam" style, I preferred "classic" because it was big and blocky and with that name I was sure to find it on both Windows and Linux platforms. Some links about styles:

This shows how to use the theme_names() function of a Style object to get a list of additional styles. I can't see how you'd want to use this more than once, unless you were writing some really fancy code. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/ttk-theme-layer.html

Here is the examples showing how to change the progressbar color, and the first example of using Style that I found. For some reason this guy thinks that "clam" is good looking style: http://stackoverflow.com/questions/13510882/how-to-change-ttk-progressbar-color-in-python

The man page for Style: https://docs.python.org/3/library/tkinter.ttk.html#tkinter.ttk.Style

Some solutions to trying to change the appearance of the progress bar. I solved my problem by just picking a different theme. This post proposes a torturous use of a canvas, while the second says to use a Style that allows for size adjustment. http://stackoverflow.com/questions/17912624/ttk-progressbar-how-to-change-thickness-of-a-horizontal-bar

Here's a man page for Progressbar that mentions that they have a Style option: https://www.tcl.tk/man/tcl/TkCmd/ttk_progressbar.htm

Here's a link that mentions somebody having a problem with their progressbar. The issue however was that the programmer wasn't properly making his code event-driven. http://stackoverflow.com/questions/16400533/why-ttk-progressbar-appears-after-process-in-tkinter

A nice man page for Progressbar that helped show the three methods that indeterminate progressbars have, start(), stop(), and step(). In the end, I found it helpful to just use the step() function for my indeterminate progressbar from my code rather than let it run on its own with start() and stop()
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/ttk-Progressbar.html



My init code for drawing the progress bars on my gui:

import Tkinter, ttk

class MyGui(self, *args, **kwargs):
def __init__(self, *args, **kwargs):
Tkinter.Tk.__init__(self, *args, **kwargs)

self.title('My GUI')

s = ttk.Style()
s.theme_use('classic')
s.configure("blue.Horizontal.TProgressbar", foreground='blue', background='blue')

f = Tkinter.Frame(self)

self.aliveness = ttk.Progressbar(f, style="blue.Horizontal.Tprogressbar", orient="horizontal", length=600, mode="indeterminate")
self.aliveness.pack(padx=10,pady=10)

self.progress = ttk.Progressbar(f, style="blue.Horizontal.Tprogressbar", orient="horizontal", length=600, mode="determinate")
self.progress["value"] = 0
self.progress["maximum"] = 100 ## Note, not strictly necessary. If not specified 100 is assumed
self.progress.pack(padx=10,pady=10)

f.pack()

Wednesday, January 6, 2016

Python Tkinter File Dialog references

The built-in tkFileDialog classes are delightfully easy to use. Note, however, that they are not in the Tkinter library, you have to import the tkFileDialog library separately, which gets you the four different types of dialog objects. Some references:

http://effbot.org/tkinterbook/tkinter-file-dialogs.htm

http://tkinter.unpythonic.net/wiki/tkFileDialog


Python Tkinter Frame border color

It turns out that in Tkinter, you can set the width of a frame's border, but you can't set it's color. Since ttk objects differ from Tkinter objects, this may not necessarily be true for ttk frames (but I haven't checked yet). The only apparent way of creating color Tkinter frames around things is to layer frames within frames. First pack a frame with the desired color as it's bg color. Then pack another frame in that one, set fill="both" with some padx and pady to set the edges of the second frame in from the edges of the first frame, allowing the color of the first frame to show around the edges of the second frame. Then pack stuff into the second frame. Some links:

https://bytes.com/topic/python/answers/38448-changing-color-default-frame-border

http://stackoverflow.com/questions/4241036/how-do-i-center-a-frame-within-a-frame-in-tkinter

For possible future reference:

http://stackoverflow.com/questions/4320725/how-to-set-border-color-of-certain-tkinter-widgets


Tuesday, January 5, 2016

Tkinter Frames are not the same as ttk Frames!

While doing a basic google search for command syntax, I stumbled across some forum threads which revealed that basic Tkinter objects such as Frame and Label have ttk equivalents with sometimes very different sets of features!

http://stackoverflow.com/questions/16639125/how-do-i-change-the-background-of-a-frame-in-tkinter

http://python.6.x6.nabble.com/LabelFrame-question-why-do-options-change-when-importing-from-tkFileDialog-td4527355.html

python tkinter tkFiledialog filetype order

For file dialogs, Tkinter very nicely allows control of the specification of file types to be shown in the dialog. This seems to actually be a native feature of the operating system that Tkinter simply has access to. To write a dialog that restricts the files that the user can chose from, you can set the file type to *.txt for instance, or a list such as [('text file','*.txt'),('shell scripts','*.csh')]. My problem is that I also want to allow the option to the user to select having access to all file types, like this: [('text file','*.txt'),('all files','*.*')], which works exactly as I want EXCEPT THAT which of these two options the dialog comes up restricting to seems completely random. It does not seem to be determinate by order of the list, or even by operating system (the problem occurs in both Windows and Linux at least in my experience). There's a "default file type" option that can be defined, but despite what seems like an obvious intention this also does not effect which of the file types out of my list is used as the default. So, I don't have an answer to this yet, but here are some results from google searches on the topic:

Here is somebody with my exact problem. Sadly nobody answered his question. I have to disagree that the issue is OS related, since the filetype dropbox default comes up differently at different times on my one single Windows system.

http://stackoverflow.com/questions/23392531/how-to-control-the-order-of-file-types-in-tkfiledialog-for-different-windows-pla


The basic man page and options for tkFileDialog:

http://tkinter.unpythonic.net/wiki/tkFileDialog


This answer didn't seem helpful to me since I've tried changing the order of my list also, but maybe it has something to do with how they're explicitly defining a dictionary for their file options? I don't know.

http://stackoverflow.com/questions/11352278/default-file-type-in-tkfiledialogs-askopenfilename-method


The original question in this thread was more simple than mine, but the discussion does show a little bit about how the definition of the file type options is a list of tuples:

https://www.reddit.com/r/learnpython/comments/3loul5/only_showing_certain_filetypes_in_filedialog/


Probably if I read up on the OS DLL for the file dialog itself, it might show how to define the order of the file type list in a way that the OS can understand, and then figure out how to do the same thing in Python. Maybe the key is to use an ordered dictionary instead of a regular dictionary?

How to write programs that run in Tkinter

There are thousands and thousands of tutorials and discussion threads about Tkinter (and by extension Tcl/Tk), but very few of them seem to address the proper approach to writing a useful program that uses Tkinter. It is easy to think first about the code you want to write to accomplish whatever task your program is supposed to do and work later on whatever GUI you'll build for your user interface. This approach however always results for me in hitting a big wall when I start coding up the GUI. That's because Tkinter (and by extension, etc) is not naitively multi threaded. It needs to either be the only thing running, or be the only thing running in a special sandbox that you make for it by implementing your own threading. It is often unhelpfully pointed out in forum threads that Tkinter is event driven. What this is meant to say is that you can't just write linear code and call routines that do Tkinter stuff; the code that you write can only consist of responses to events in your Tkinter GUI. Essentially, Tkinter is the language of whatever program you are writing; it just happens to have pythonic syntax. It's sort of like how C++ has C-like syntax. This seems terribly unfair at first, but after you decide to do it Tkinter's way it turns out to be not so bad. I've written GUIs using both methods, but these day's I'm finding it easiest to let my programs be Tkinter programs instead of Python programs that use Tkinter.

The breakthrough for me was an example that I found that was structured like an executive loop, although it wasn't presented as such by it's author. What was happening in this example was that the __init__ method was calling a start() method that then called a function that ran some code and then called itself again after a short wait using after(). mainloop() then serviced each of the calls of the executive function as well as the rest of the GUI. Here is that example:


So, the structure of the python __main__ routine for a useful Tkinter program needs to be:

1. define your top-level GUI object, which will call it's __init__. In that __init__ you build all the objects in your control screen and define its appearance.
2. If you don't do it in your __init__ method, you can call your GUI's start() function from your main python routine. But it won't run because mainloop() isn't running yet.
3. call your GUI's mainloop(). Now your start() function will run. It will need to do something that causes itself or another function to be scheduled to be run by mainloop(). The best thing to do here is to have an executive function that functions like a state machine; each time the executive is entered, it checks what the current state is and executes the code for that state, and decides whether to change the state variable before rescheduling itself.
4. Somewhere in your GUI's functions find a place to put a call to your GUI's quit() function, which will exit mainloop().
5. Now you're back in your python main function, where you can either do something else or exit.

Since most programs are basically linear, it is very easy to break them up into state machine pieces. One could even argue that all programs are state machines to begin with.

Here are some of the links that helped me figure this out:

Presently my go-to link that helped me get the idea:

http://stackoverflow.com/questions/29158220/tkinter-understanding-mainloop


From the above link, another example of the ball game with some better defined executive loop examples:

http://stackoverflow.com/questions/25430786/moving-balls-in-tkinter-canvas/25431690#25431690


http://stackoverflow.com/questions/8683217/when-do-i-need-to-call-mainloop-in-a-tkinter-application

https://www.reddit.com/r/learnpython/comments/2rpk0k/how_to_update_your_gui_in_tkinter_after_using/

This one has great discussions about both the threading and executive loop methods:

http://stupidpythonideas.blogspot.com/2013/10/why-your-gui-app-freezes.html


This question and answer sort of addressed the underlying Tkinter philosophy that is part of this topic:

http://programmers.stackexchange.com/questions/213935/why-use-classes-when-programming-a-tkinter-gui-in-python


A google search which brings more good answers of this sort:

https://www.google.com/#q=python+tkinter+define+code+to+be+run+with+mainloop()+is+called


One other tip that is helpful is that it's possible to call update() on your main screen from within the executive loop function to make things appear and move on the GUI while code is executing in the executive loop. In my example, one of my states has a loop of data processing that I want to show progress on; I can set the progress bar value and call update() for each iteration of the loop.



Python Tkinter layout managers

As I am becoming better at using Tkinter, I am gaining a better appreciation for how to use the different layout managers. It turns out that there are three of them, of which I currently most like to use "pack" and "grid" (the third being "absolute", which is both less portable and less elegant than the other two). Here are some bookmarks to the information that I'm making the most use of.

First, some good general tutorial pages:
http://zetcode.com/gui/tkinter/layout/

About pack():

What I have learned is that it's a very quick method of building up display panels using a paradigm of block stacking. You have to define objects, then you place objects within objects in stacks. If you don't use a "side" argument, whatever object you pack gets stacked under the previous objects. A string of pack() commands with "side" arguments get placed next to each other at the same level. Objects can be stretched to touch sides on one or both axes of the object they've been packed into. It's nuts primitive but so easy and quick.

Here is a description of the function with its parameters and a couple of go-to examples:

http://effbot.org/tkinterbook/pack.htm


some of the same examples as in the above link but more nicely presented:

http://www.python-course.eu/tkinter_layout_management.php


grid() is like doing HTML tables, very intuitive and handles resizing well.





Friday, January 1, 2016

Python Tkinter destroy widget

For my status screen, I'd like to have some things come and go depending on what's happening. No problem; in Tkinter each widget has a destroy function. This was the first example I found; although the question in this forum thread is about how to make destroy() work, the actual problem was an interesting reminder of the importance of watching one's scope when assigning the results of a widget init!

http://stackoverflow.com/questions/17673662/tkinter-removing-a-button-from-a-running-program

While working on this feature I also leaned that depending on the layout manager being used and how the frame was defined, using destroy() could result in resizing of the frame. This tutorial covers layout managers excellently:

http://zetcode.com/gui/tkinter/layout/

Python tkinter progress bar

Google keeps bringing me the same four examples for how to do a tkinter progress bar. It turns out that one of them is good in that a commenter showed how to implement a progress bar screen as a class which is how my entire project is constructed. Here's the link:

http://stackoverflow.com/questions/7310511/how-to-create-downloading-progress-bar-in-ttk

I reformatted the commenter's very helpful example to make the window quit when 100% is reached. I had to feel my way around how to add the quit and the destroy; I tried making a root object from the launch of Tk() and using it's quit and destroy, but for some reason that didn't work like it did in my other display classes. So in the end, here is my slightly modified version of the original program:

import Tkinter as tk
import ttk


class SampleApp(tk.Tk):

def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
##root = self.root = tk.Tk()
##root.title('root title')
self.button = ttk.Button(text="start", command=self.start)
self.button.pack()
self.progress = ttk.Progressbar(self, orient="horizontal",
length=200, mode="determinate")
self.progress.pack()

self.bytes = 0
self.maxbytes = 0
def start(self):
self.progress["value"] = 0
self.maxbytes = 50000
self.progress["maximum"] = 50000
self.read_bytes()
def read_bytes(self):
'''simulate reading 500 bytes; update progress bar'''
self.bytes += 500
self.progress["value"] = self.bytes
if self.bytes < self.maxbytes:

# read more bytes after 100 ms
self.after(100, self.read_bytes)
else:
self.quit()
app = SampleApp()
app.mainloop()
app.destroy()

Python Tkinter Label widget set font size and bold without setting font

For user feedback, some kind of text in a frame that can be updated to show status is essential. It turns out that the Label widget is set up to do this naitively. Just define a text variable type and give the name of that variable during the setup of the Label widget and any changes to the text variable automatically show up in the widget. Pretty basic. The second-to-last example in this amazing tutorial demonstrate this with a slider that returns values and a label which is set up to dynamically display whatever the value of the slider is:

http://zetcode.com/gui/tkinter/widgets/

Basically, this entire site's tutorial is incredible and finally something pertinent to what I am doing:

http://zetcode.com/gui/tkinter/

The thing that I found out about which is more interesting to me is how to format the font in the Label widget. I personally didn't care to change the font itself from whatever was the system's default, but I wanted to make it bigger and make it bold. It turns out that this is also no problem; for the "font" specification you just omit specifying the font itself and just specify the size and weight, as in these links:

http://stackoverflow.com/questions/4072150/how-to-change-a-widgets-font-style-without-knowing-the-widgets-font-family-siz

http://effbot.org/tkinterbook/label.htm

So, the code that I made looks like this:

self.var = StringVar()
self.label = Label(self, text=0, textvariable=self.var, font="-size 20 -weight bold")

Tuesday, December 29, 2015

Tkinter binding Return key to an Entry widget

For my GUI for my project, I wanted to pop up a text entry widget to get a parameter for the user, and have it disappear and return whatever was entered when they pressed the "Return" key. Several examples of using the Entry widget had an "Enter" button, but that would be too much extra mouse motion for my customers (it makes sense for a form with several parameters). I'm also still not to the point of programming an entire GUI, I'm just trying to pop up some windows whenever my program needs input, so in that regard I'm already not doing this right. However, I copied the example methodology that I used to make the button window earlier, which was to define a class for the general type of dialog I wanted, and then a method that instantiated an object of that class, ran it, destroyed it, and returned the result. On my windows version of python, things were a little bit different than in some examples that I found, and the big trick was getting the "Return" key binding to work right.

Here are the top two links that I found for using Entry widgets. They don't directly show using a class (both have the call to mainloop() right in line with the rest of the code).

This link is slightly lighter in example code than the next, but it shows use of get(), focus_set(), delete(), and insert(). Like the next link, its example uses a button to call get(). This link has the example of how to set the width using entry.config(width=width).

http://effbot.org/tkinterbook/entry.htm


This one shows how to insert default text into entry widgets and how to use the get() method. It also shows the delete() method for clearing an entry widget before inserting default text, but the examples use some kind of 'END' keyword that didn't work for me, and for me the widgets started empty anyhow. It then has some examples of nice formatting, but I actually just wanted to have the text entry frame and have the user prompt about what to enter in the title bar:

http://www.python-course.eu/tkinter_entry_widgets.php


Both of the above links seemed to show the Entry widget being imported from Tkinter, but in my case, it turned out that for me for some reason I couldn't find Entry in the Tkinter library, instead it was in the ttk libary. I seem to have lost the link that gave me the clue to try that.

Here's the example of how to use the root window .title property to make the text I wanted appear in the title bar of the entry window:

https://bytes.com/topic/python/answers/34607-how-change-text-title-bar-tkinter-windows



The next few links had clues about binding the Return key to a callback. However none of them were using the Entry widget in a class like I was, so the way that they had the callback contain references to the entry object didn't work for me.

Here's a really basic example showing binding, but doesn't show using the get() function:

http://stackoverflow.com/questions/16996432/cant-figure-out-how-to-bind-the-enter-key-to-a-function-in-tkinter


This one showed a callback, but I didn't notice until much later that it's doing something interesting by passing a "textvariable" parameter into the definition of the Entry widget that the callback is then accessing. This is the use of a "StringVar" type that one of the previous links disparaged and which I was trying to do without.

http://www.java2s.com/Code/Python/GUI-Tk/BindenterkeytoEntry.htm



The following super basic example turned out to have the solution, in that it showed the callback bound to the Return key using the event argument to the callback to find a pointer the entry window so that it's get() function could be called:

https://mail.python.org/pipermail/tkinter-discuss/2008-June/001447.html


So, here is the first version of the code that seemed to work for me:

class NoButtonEntry(object):
def __init__(self, title, default):
root = self.root = Tkinter.Tk()
root.title(title)

e = ttk.Entry(root)
e.config(width=(len(title)+20))
if (default):
e.insert(0,default)
e.bind('',self.return_it)
e.pack()
e.focus_set()

root.protocol("WM_DELETE_WINDOW", self.close_thing)
root.deiconify()
root.lift()
root.call('wm', 'attributes', '.', '-topmost', True)
root.call('wm', 'attributes', '.', '-topmost', False)
def return_it(self, event=None):
self.returning = event.widget.get()
self.root.quit()
def close_thing(self):
self.returning = None
self.root.quit()
def mentry(title,default=None):
newentry = NoButtonEntry(title,default)
newentry.root.mainloop()
newentry.root.destroy()
return newentry.returning
serial = mentry(title='Enter text or return to accept default',default='0')

Update: So, later I went on to try to add an "accept" button to my pop-up and found myself not able to make that work either. The solution turned out to be so simple, after looking at example after example. If I defined the entry object as an object within my class, by saying something like self.e = ttk.Entry(root) instead of e = ttk.Entry(root), then the callback for my button could find the entry object! Doing it the other way made the declaration of the entry object local only and not accessible to other objects in the class In the end, I learned to just always make objects in my GUI class members of that class so that they could all access each other.

Here is just a great general tutorial on how use Tkinter including how to define entry and button objects, with solid examples:

http://python-textbok.readthedocs.org/en/latest/Introduction_to_GUI_Programming.html


Here is the example that probably helped me figure it out, because the OP shows doing it the way I was trying to do it, and somebody answers with an example of doing it correctly:

http://stackoverflow.com/questions/9355742/python-tkinter-button-entry-combination


Oh, and here's more info on how to set the focus to the entry widget. Not sure why I didn't use this except that the answer seems to be for a Tkinter Entry widget and I'm using a ttk Entry widget:

http://stackoverflow.com/questions/13626406/setting-focus-to-specific-tkinter-entry-widget


So now my code looks like this:

class NoButtonEntry(object):
def __init__(self, title, default):
root = self.root = Tkinter.Tk()
root.title(title)

self.e = ttk.Entry(root)
self.e.config(width=(len(title)+20))
if (default):
self.e.insert(0,default)
self.e.bind('',self.return_it)
self.e.pack(side="left")
self.e.focus_set()

self.b = Tkinter.Button(root,text="Accept",command=self.return_it)
self.b.pack(side="left")

root.protocol("WM_DELETE_WINDOW", self.close_thing)
root.deiconify()
root.lift()
root.call('wm', 'attributes', '.', '-topmost', True)
root.call('wm', 'attributes', '.', '-topmost', False)
def return_it(self, event=None):
# Note I no longer have to care about whether it was from an event
self.returning = self.e.get()
self.root.quit()
def close_thing(self):
self.returning = None
self.root.quit()
def mentry(title,default=None):
newentry = NoButtonEntry(title,default)
newentry.root.mainloop()
newentry.root.destroy()
return newentry.returning
serial = mentry(title='Enter text or return to accept default',default='0')

Wednesday, November 18, 2015

A python app with Tcl/Tk gui file selection on a BeagleBone with LCD7 touch screen

Trying to pull together a demo user interface for my GSE box, making use of the python code from the previous demo and the hardware that I have on hand which is a BeagleBone with the LCD7 touchscreen. Along the way, a whole lot of involuntary education is occurring, so I'm documenting the journey here.

The plan:
1. Get the BeagleBone powered and figure out how to get the touchscreen working
2. Figure out if the OS for the BeagleBone has python.
3. Study the python code from the demo and glue in tcl/tk file selection dialogs where appropriate to make it more user friendly.
4. Migrate everything over to the Raspberry Pi. Supposedly all the python will be nicely portable.

Notes for each step:

1. Get the BeagleBone powered and figure out how to get the touchscreen working:

Fortunately for me, this was all already working. The hardest part was making sure that I had the polarity of my power chord correct (center positive) and that I was using a supply capable of powering the screen (needed to be 2A). In the end, I am temporarily fudging it with my cut USB chord with power plug wired on that I made for testing the GSE circuit board. The first USB power block that I tried was only 1.2A and this was insufficient, but most dual power blocks supply 2A and one of those runs it just fine. The Beagle Board website actually has very helpful links to recommended power supplies from DigiKey (http://beagleboard.org/peripheral). The Beagle Board powered up fine over USB and I'm able to use my trusty PuTTY to log into it using the default password over the USB cable. With the 2A power to the LCD7, the board finds the screen automatically, and loads a desktop GUI without any fiddling required. It turns out that the microSD card that came with the BeagleBone has a Linux distribution called "Angstrom" that is fully integrated with a Gnome desktop. The only disappointment is that the touchscreen is barely able to control this GUI without a keyboard and mouse. It is possible to drag the mouse pointer around with a finger to the screen, click by pressing, but the resolution of the GUI is so high that it's difficult to position the mouse pointer precisely enough by touch, and there doesn't seem to be an on-screen keyboard (I got an on-screen keyboard in a later Angstrom distribution that I tried, but it's not like an iPhone where it pops up when needed and is optimized for mobile use).

2. Figure out if the OS for the BeagleBone has python.

Once I got a USB keyboard and mouse connected, starting a terminal window and typing "which python" answered the question of whether python was installed. Directly starting python by just typing "python" got me the python prompt. But then, shockingly, "import Tkinter" failed. I could import other standard libraries and execute other python commands, but no Tkinter, tkinter, or _tkinter. Googling has revealed that everybody else has this problem as well. A variety of solutions have been tried, apparently. These include: a) Doing package updates in Angstrom to get Tkinter, but it seems that a simple package update is not sufficient and "rebuilding python" is required! That sounded like way too much, but somebody on a forum thread suggested b) trying out Ubuntu instead. Sadly, I've learned that the BeagleBone distribution of Ubuntu does not come with a desktop interface installed by default like in Angstrom, and its python distribution doesn't come with Tkinter either. However, in the process I learned how to get disk images for both Angstrom and Ubuntu and load them to a microSD card from the safety of my Windows laptop. Notes follow:

b. Get Ubuntu:

I started by first trying to download a new distribution of Angstrom and booting the BB from it, and it worked great. The following link contained perfect handholding for how to get the image file, where to get 7Zip for unpacking it, and where to get a Windows app for writing it to an SD card:

http://beagleboard.org/getting-started#update

Interestingly, the SD card writer app is apparently distributed by Ubuntu. Here is Ubuntu's own page about the App (and other methods of writing SD card images that don't apply to my attempts to do it from Windows):

https://help.ubuntu.com/community/Installation/FromImgFiles

The new Angstrom distribution booted up to the desktop like the original Angstrom distribution that came with the Beagle Board, but still didn't have Tkinter installed.

I was able to repeat the same image installation steps from the above guide while using the Ubuntu image pointed to from here:

http://elinux.org/BeagleBoardUbuntu#BeagleBone_White.2FBlack.2FGreen

Trying it out, I got the familiar screenfuls of grubloader lines on the LCD7, which means that it found the screen. However, no GUI! I logged in using the default password for the distribution, did a "which python" and "python" and to my surprise, still no Tkinter! While I had learned how to get Ubuntu, it hadn't solved my problem, and it turns out that I have to install a gui manually. As far as I can tell, this also means doing the step which I've so far tried to avoid which is putting the BeagleBone on a network. This is something that can't do at work, ironically enough, because of our locked-down IP assignments, and I don't have a Wi-Fi cape for the Beagle Bone, which means the only place I have to plug it into is my router at home.

Here's probably the best link which explains which packages to download and install to get Tkinter in Ubuntu and mentions the command to do the rebuild (the first comment mentions it, but scroll down to the nicer example in the second comment). This thread in particular applies to my case because I noticed that my distribution of Ubuntu had python 2.7 like the OP of this thread:

http://stackoverflow.com/questions/11752174/how-to-get-tkinter-working-with-ubuntus-default-python-2-7-install

This is the link that describes needing to install tk-dev!

The above link points to this sort of unrelated thread which has a very nice reply from someone about the general process for building Python in Ubuntu:

http://stackoverflow.com/questions/6171210/building-python-and-more-on-missing-modules

So, I read a bunch of threads about how to get a GUI for Ubuntu. Most of them agree that for BeagleBone the best one to try is LXDE. Sadly, a lot of people out there trying to make this work are using a Beagle Bone Black which is not what I have and also has on-board flash memory for the OS which I don't have.

Here is a long forum thread with a lot of clues about downloading the LXDE package and getting it to install. The scripts described from the "tools" directory seem to be on the Beagle Bone Black and not on the Ubuntu distro that I have. There's also some warnings about problems with Gnome utilities, which wouldn't affect me trying to install LXDE but I have Gnome in the Angstrom distribution and you never know when a customer is going to try to open something:

https://groups.google.com/forum/#!msg/beagleboard/TtcD9vfOzT8/1stwCBZqNRAJ

This link is mostly about BBB but is hopefully still instructional for my case:

http://elinux.org/Beagleboard:Installing_LXDE

A third option, c, is to try the BeagleBone distribution of Debian from the following link. There might be more of a chance that it will come up with a GUI desktop; BeagleBone's Angstrom distribution which had a GUI comes from the same page, and the Ubuntu distribution actually didn't come from any of BeagleBone's pages. Although reading the comments it seems that the latest Debian image from the BeagleBone page is corrupted so going down this path my end up sucking.

http://beagleboard.org/latest-images

However, when I tried it, the Debian image downloaded and booted fine and went to a GUI. It's a lot faster than Angstrom and looks nicer but doesn't have a lot of applications. It seems to be LXDE. It still doesn't have Tkinter, producing the exact same error message when I try to import it. Supposedly Debian is derived from Ubuntu or vice versa. So I launched into some more online searches:

Keying off of the specifics of the message:

Some search results from the message: File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 42:
This thread which describes the exact message, plus the "Package python-tk has no installation candidate" message I get when trying to do the installation: http://ubuntuforums.org/showthread.php?t=1959073
A thread in which somebody solved the problem by installing the library using "Synaptic", and there's also some rage about why they wouldn't just include the library. Note that I don't seem to have Synaptic on my two working Beagle Bone distros: http://askubuntu.com/questions/513981/tkinter-on-ubuntu-14-04-seems-not-to-work
A page about How to install Tkinter, however it doesn't seem to mention needing tk-dev as another search seems to have turned up: http://tkinter.unpythonic.net/wiki/How_to_install_Tkinter

Some search results from: E: Package 'python-tk' has no installation candidate:
A thread that describes what I'm seeing exactly, but no apparent solutions. A link to a python man page about Tkinter, some suggestions to check the repository list (which is probably my issue since I'm not even on a network)http://ubuntuforums.org/showthread.php?t=361342
The python wiki page mentioned above, which includes such interesting categories as "Tkinter Folklore"https://wiki.python.org/moin/TkInter
An interesting thread with a newbie in which it is eventually resolved that he's not getting to the mirrorshttp://ubuntuforums.org/showthread.php?t=1629083

Searching for Sneakernet options for doing package updates:
A thread full of different ideas, like using Synaptic, an offline repository program, apt-mirror, using apt-get with some kind of verbose arguments, and other hints: http://askubuntu.com/questions/974/how-can-i-install-software-or-packages-without-internet-offline
A short thread where somebody gives a link to Ubuntu's packages web page: http://ubuntuforums.org/showthread.php?t=1488395
A hint which seems to be to run apt-get while navigated to the thumb drive, an interesting idea. Also mentions running dpkg: http://www.linuxquestions.org/questions/linux-newbie-8/install-new-packages-from-usb-drive-using-apt-get-4175417568/
Another instructional about using dpkg: http://www.pendrivelinux.com/how-to-install-deb-packages/

So, in the end, I brought the board home and plugged it into my FiOS router with the Debian OS installed; it got a DHCP address immediately and got on the network with no issues. I began following the instructions for doing package updates for Ubuntu using apt-get, and they worked very smoothly. I had to do the following:

sudo apt-get update
sudo apt-get install python-tk

And that seemed to fix it! From within the python prompt, I was able to import Tkinter and launch dialog boxes like I plan to do with my final program. Apparently, rebuilding python as described in the Ubuntu links was not required.

After getting that to work, I was curious to see if I could get it to work for Angstrom. Angstrom doesn't seem to use apt-get, it uses opkg. I did

opkg update
opkg list | grep tkinter
opkg install tkinter

(I have to check that the last line is exactly the one that I did). Anyhow, it didn't work. I also tried:

opkg install tk-dev

It downloaded and installed, but still no love from "import Tkinter". I was unable to "rebuild" python according to the instructions in the one link that explained it because Make failed with some error message that didn't make any sense, and I couldn't find a "configure" script in the python lib directory. It is probably likely that I haven't actually downloaded the python source directory.

Rather than address that problem directly, I decided to turn back to getting a GUI for the Ubuntu distribution, now that I'd overcome my fear of putting the Beagle Bone on my home network and had gotten through a cycle or two of downloading and installing packages. One of the many web searches I'd done had shown some much nicer-looking Tcl/Tk dialog boxes for a Ubuntu desktop which makes sense since Tkinter uses whatever API is native to the OS. After some searching through links like the following:

http://www.makeuseof.com/tag/10-top-linux-desktop-environments-available/
http://www.javacodegeeks.com/2014/02/5-most-awesome-desktop-environments-for-ubuntu.html

I decided that the desktop I saw must have been the "Unity" desktop which is the one that is most often used with Ubuntu anyhow. It looks like this:

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgokrjrPlbVvsolhVc4Gvl5Fx9-RealsRXfJbUyT-QS-s0v8WZEKjLhHAE_8H8fGGD8UgBqAmvyIiHdfTqQwt64RQRKcwEKLW_tHwI0cEk83i_IMjwegtL7qMzDX7hDgiSMC9wPChIeHEvB/s1600/ubuntu-precise-apps-screenshot.png

So to get Unity, there seems to be a circle of links describing how to install it on an SD card for Beagle Bone. Some of these are for BBB which I don't have, and some talk about "expanding" the disk partition on the SD which again sounds like a lot of work. I guess I should try these though:

http://elinux.org/Beagleboard:Ubuntu_On_BeagleBone_Black#Ubuntu_Precise_On_Micro_SD
http://elinux.org/Beagleboard:Desktops_On_Ubuntu/Debian#Ubuntu_Precise_On_A_microSD_With_Ubuntu_Desktop

I'm not sure yet what the difference is between Unity and Precise. Unity is supposedly the default desktop, while the links mention Precise, and they both look the same in Google image searches.

Here's an instructible about installing Ubuntu with LXDE, presumably the procedure for installing Unity would be similar. Since I apparently already have LXDE with my Debian install, my interest is in trying to experiment with something visually different:

http://www.instructables.com/id/BeagleBone-Ubuntu-OS-LXDE-GUI/