As the title suggests, I wrote a wrapper for Tkinter that can easily call the GUI like the interactive widget of IPython Notebook, so I will give it up. I'm worried because it's behaving a little unexpectedly, so I'd like to ask for your teaching. Apart from that, I think it's much easier to use, so I'm thinking of porting it to Qt or Gtk next time.
Postscript: Ported to Gtk (link)
I've been using IPython notebooks a lot these days, but when I think about what features I'm attracted to, I can interactively change variables and execute functions, such as interactive and interactive. I think it depends largely on the existence. The rest is creating slides. I've been writing my own wrapper in Tkinter, but I was impressed by the ease of use of the interactive widget in IPython Notebook, so I decided to make something that could display the Tk widget in a similar way. .. Therefore, how to use
It is almost the same as the one listed in. Due to the Tkinter module, the drop-down list has been changed to a radio button. However, some people are creating dropdowns with Tk,
It seems that there is nothing you can't do if you incorporate something, but this time I gave it up. (I'm going to use Gtk as the main ...)
The type to call is the same as [Using Interact] interact as mentioned earlier. It is grouped as a class interactive, and when called from a script, it is necessary to execute w.display () for display.
>>> from tk_wrapper import interactive
>>> def f(a,b=20):
... return a + b
...
>>> w = interactive(f, a=(0, 20, 2), b=20)
(in console, tk widget appear here)
(you can add buttons and label here manually)
(then, you should add the next line)
>>> w.display()
(and you can get the result for f(a,b) by)
>>> w.result
32
(or get the arguments with a dictionary)
>>> w.kwargs
{'a':8, 'b':24}
Also, at the head family, when specifying arguments with interactive, an error will occur if all the same arguments as the arguments of the function f to be linked are not included. You need to specify the fixed class as c = fixed (10) for the arguments that you do not want to change. Since it was difficult to implement this time and I didn't have many chances to use it, if the default value is set in the argument of the function f linked by the variable not specified by interactive, it can be executed using that value as it is. Yes (of course, if you don't specify it, you will get a type error saying "There are not enough arguments").
It's long (although it's a lot simpler than the original one), but please take a look if you like. It has been confirmed to work with Python 2.7.6, so please try it. Even in Python3.4.0, if you change "Tkinter" to "tkinter", make the print statement a function notation, and then take out the dictionary value in 3 series
>>> a = {"d": 1, "c":4, "b":5}
>>> a.values()
dict_values([4, 5, 1])
Because it becomes like
>>> list(a.values())
[4, 5, 1]
I was able to execute it just by replacing the part of arg.value () (lines 97,116) with list (arg.value ()) as shown in.
tk_wrapper.py
#! /usr/bin/env python
# -*- coding:utf-8 -*-
#
# written by ssh0, October 2014.
__doc__ = '''IPython-like Tkinter wrapper class.
You can create Scalebar, Checkbutton, Radiobutton via simple interface.
usage example:
>>> from tk_wrapper import interactive
>>> def f(a,b=20):
... return a + b
...
>>> w = interactive(f, a=(0, 20, 2), b=20)
(in console, tk widget appear here)
(you can add buttons and label here manually)
(then, you should add the next line)
>>> w.display()
(and you can get the result for f(a,b) by)
>>> w.result
32
(or get the arguments with a dictionary)
>>> w.kwargs
{'a':8, 'b':24}
'''
import Tkinter
import inspect
class interactive(object):
def __init__(self, func, title='title', **kwargs):
self.__doc__ = __doc__
self.func = func
self.kwargs = dict()
args, varargs, keywords, defaults = inspect.getargspec(self.func)
d = []
for default in defaults:
d.append(default)
self.kwdefaults = dict(zip(args[len(args)-len(defaults):], d))
self.root = Tkinter.Tk()
self.root.title(title)
self.f = Tkinter.Frame(self.root)
for kw, arg in kwargs.items():
kw = str(kw)
arg_type = type(arg)
if arg_type == tuple:
# type check for elements in tuple
argtype = self.type_check(arg)
if argtype == str:
self.radiobuttons_str(kw, arg)
else:
self.scale_bar(kw, arg, argtype)
elif arg_type == int or arg_type == float:
self.scale_bar(kw, [arg], arg_type)
elif arg_type == bool:
self.checkbutton(kw, arg)
elif arg_type == str:
self.label(arg)
self.kwargs[kw] = arg
elif arg_type == dict:
self.radiobuttons_dict(kw, arg)
else:
raise TypeError
self.f.pack()
self.status = Tkinter.Label(self.root, text=str(self.kwargs))
self.status.pack()
def display(self):
self.root.mainloop()
def status_change(self):
self.status.config(text=str(self.kwargs))
self.kwargs_for_function = self.kwdefaults
for k, v in self.kwargs.items():
self.kwargs_for_function[k] = v
self.result = self.func(**self.kwargs_for_function)
def set_value(self, var, kw, new_value):
# if argument is already given in func, use it for a default one
if kw in self.kwdefaults:
var.set(self.kwdefaults[kw])
self.kwargs[kw] = self.kwdefaults[kw]
else:
var.set(new_value)
self.kwargs[kw] = new_value
def type_check(self, arg):
argtype = type(arg[0])
if not all([type(a) == argtype for a in arg]):
raise TypeError("""types in a tuple must be the same.
int or float: Scalebar
str : Radiobuttons""")
return argtype
def scale_bar(self, kw, arg, argtype):
def scale_interact(new):
self.kwargs[kw] = var.get()
self.status_change()
# length check for tuple
len_arg = len(arg)
if len_arg > 3 or len_arg == 0:
raise IndexError("tuple must be 1 or 2 or 3 element(s)")
if argtype == int:
var = Tkinter.IntVar()
scale_resolution = 1
elif argtype == float:
var = Tkinter.DoubleVar()
scale_resolution = 0.1
else:
raise TypeError("arg must be int or float")
# set the values
if len_arg == 3:
scale_from = arg[0]
scale_to = arg[1]
scale_resolution = arg[2]
elif len_arg == 2:
scale_from = arg[0]
scale_to = arg[1]
else:
scale_from = arg[0]*(-1)
scale_to = arg[0]*2
self.set_value(var, kw, arg[0])
# create scale widget in frame
scale = Tkinter.Scale(self.f, label=kw, from_=scale_from, to=scale_to,
resolution=scale_resolution, variable=var,
orient='h', width=10, length=200,
command=scale_interact)
scale.pack()
def checkbutton(self, kw, arg):
def check_interact():
self.kwargs[kw] = var.get()
self.status_change()
var = Tkinter.BooleanVar()
self.set_value(var, kw, arg)
checkbutton = Tkinter.Checkbutton(self.f, text=kw, variable=var,
command=check_interact)
checkbutton.pack()
def radiobuttons_str(self, kw, arg):
def select():
self.kwargs[kw] = var.get()
self.status_change()
var = Tkinter.StringVar()
label = Tkinter.Label(self.f, text='select one for '+kw)
label.pack()
self.set_value(var, kw, arg[0])
for x in arg:
R = Tkinter.Radiobutton(self.f, text=x, variable=var,
value=x, command=select)
R.pack()
def radiobuttons_dict(self, kw, arg):
def select():
self.kwargs[kw] = var.get()
self.status_change()
vtype = self.type_check(arg.values())
if vtype == str:
var = Tkinter.StringVar()
elif vtype == int:
var = Tkinter.IntVar()
elif vtype == float:
var = Tkinter.DoubleVar()
else:
raise TypeError("arg must be int or float or str.")
label = Tkinter.Label(self.f, text='select one for '+kw)
label.pack()
self.set_value(var, kw, arg.values()[0])
for k, d in arg.items():
R = Tkinter.Radiobutton(self.f, text=k, variable=var,
value=d, command=select)
R.pack()
if __name__ == '__main__':
import sys
import Tkinter
def f(x, y, z, o=True, i=0):
print x, y, z, o, i
def b1():
print w.kwargs
buttons = [('b1', b1), ('exit', sys.exit)]
w = interactive(f, x=(0, 10), y=(1, 100, 10),
z=("ZZZ", "III", "foo", "bar"),
i={'0':0, '10':10, '20':20},
o=True
)
for button in buttons:
button = Tkinter.Button(w.root, text=button[0], command=button[1])
button.pack()
w.display()
The problem is that, as you can see, the order in which the variables are displayed is out of order. I think this is due to the fact that it is referenced in dictionary format, but I don't know how to solve it, so I'm leaving it as it is for the time being. This is where I want to do something about it. Maybe you can use Ordered Dict.
Then, as you can see from the gif image and the image above, the contents of the function f have been executed by the number of arguments specified in scalebar at the beginning. Perhaps this is Tkinter's Scale specification, which means that the value was set at the stage of display. It didn't work to enable scale_change on the line just before self.root.mainloop () in the display function. If you get the same problem when porting to Gtk, the cause may be different. Therefore, like the IPython Notebook, the referenced function should be a light function (or a completely dummy function), and I feel that it is more stable to perform heavy calculations when the button is pressed.
As for the future prospects, I would like to extend it to Gtk for the time being, and in that case I would like to incorporate dropdowns, tabs, and progress bars.
Recommended Posts