On 20.01.2005, at 12:24, Mark English wrote:

I'd like to write a Tkinter app which, given a class, pops up a
window(s) with fields for each "attribute" of that class. The user could
enter values for the attributes and on closing the window would be
returned an instance of the class. The actual application I'm interested
in writing would either have simple type attributes (int, string, etc.),
or attributes using types already defined in a c-extension, although I'd
prefer not to restrict the functionality to these requirements.

I am working on nearly the same thing! Small sort of generic attribute editor
in Tkinter (and Tix). Altough my implementation is still very unpythonic
(== ugly) in many places, it can edit the attributes of instances and classes,
as well as generate new instances / classes. I would like to create new
attributes or delete them as well, but haven't done it so far.
A still faraway dream would be, to allow the user to enter source code, that
will be compiled into byte code and assign it to the instance, so the user can
modify code at run time (if it is possible to disallow access to insecure
modules while code is compiled).


Secondly, the code won't know exactly how to initialise the class
instance used to determinte the attributes. Do I need to make it a
prerequesite that all instances can be created with no arguments ?

I did it this way, too.
Maybe you can provide a parameterless __new__ in addition to the __init__.
I imagine this is the way that e.g. pickle does the job. Well, I don't
know. Let this sentence just be an invitation for an expert to write something
here.


Should I force/allow the user to pass an instance instead of a class ?

However you like. I prefer passing classes, otherwise you end up in a situation where you need to create dummy instances that are only used as "copying templates". If you get an instance: foo = type(bar)() would give you an instance of the same class as bar. But this just looks like a hack to me when compared to foo = Bar().

Passing an instance allows you to look at its __dict__ of course.
But you have no assurance that the variables you find there are present
in all instances of that class. Phython is just to dynamic for that.

Should I be using inspect.getargspec on the class __init__ method and
then a loop with a try and a lot of except clauses, or is there a nicer
way to do this ? Presumably the pickling code knows how do
serialise/deserialise class instances but I'm not sure how I'd use this
without already having a class instance to hand.

Personally, I ended up writing a class Attribute that provides access to
the attributes and allows more finetuning than a generic approach would
do (think of e.g. validation). My overall setting works like this:
For each attribute that you want to appear in the GUI you define an Attribute


class Attribute(object) :
        def __init__(self,
                     name,               # name of the attribute
                     type,               # specifies the widget type
                     values=None,        # then OptionMenu is used instead
                     validate=None,      # if given, a callable that returns
                                      # either True or False
                     get=None,           # callable to get the actual value
                                      # None: generic getattr()
                     widget_options={}   # passed to the Tix widget used
        ) :
                pass # [...]

The "controller" of an editable object gets the object and a list
of those Attribute()'s. There is a generic one that handles validation
and generic setattr(), but can be overwritten to allow more sophisticated
stuff.


When creating a new instance, I ask for initial arguments which I know
(like __name__ and __doc__ if its a class that I create) in nearly the
same way.

If you already have your instance, it is possible to generate the
Attribute()-list from the dict of this instance:

attributes = [
Attribute(name,type(getattr(instance,name))) for name in dir(instance)
]


Lastly, does such an app already exist ?

As you see, my solution is not as general as what you might have in mind.
I needed to finetune so much of the underlying generic plan, that it
looks more like declaration-based now.


Anyway, if you are interested: This is the edit part of my app (It's only
the mapping from attributes to widgets, so no instance creation in this part).
As this was only a short snippet in the project I am doing, I could, never
give it the time it deserved. So I am sure that my code is not the best sollution,
but maybe it serves as a starting point for further discussion.


All the best,

- harold -



import Tkinter
import Tix

class Attribute(object) :
        def __init__(self,
                     name,
                     type,
                     values=None,
                     validate=None,
                     get=None,
                     widget_options={}
        ) :
                if not get : get = lambda obj : getattr(obj,name)

                self.name = name
                self.type = type
                self.values = values
                self.get = get
                self.validate = validate
                self.widget_options = widget_options
                self.label = None
                self.widget = None


class Edit(Tkinter.Frame) : import types

        def __init__(self,obj,items) :
                self.obj   = obj
                self.items = items

        def create_widgets(self,master,**options) :
                Tkinter.Frame.__init__(self,master,**options)
                for item in self.items :
                        if item.values :
                                widget = Tix.OptionMenu(master,label=item.name)
                                for v in item.values :
                                        widget.add_command(str(v),label=str(v))
                                item.label = None
                                item.widget = widget
                        else :
                                widget = self.widget_types[item.type](master)
                                
widget.config(label=item.name,**item.widget_options)
                                item.label = widget.label
                                item.widget = widget.entry
                        widget.pack(side=Tkinter.TOP,fill=Tkinter.X)
                self.pack()

        def update_widget(self,item) :
                if isinstance(item.widget,Tix.OptionMenu) :
                        item.widget.set_silent(item.get(self.obj))
                else :
                        item.widget.delete(0,Tkinter.END)
                        item.widget.insert(0,item.get(self.obj))

        def update_widgets(self) :
                for item in self.items :
                        self.update_widget(item)

        def validate(self,item) :
                if isinstance(item.widget,Tix.OptionMenu) : return True
                value = item.widget.get()
                if item.validate :
                        valid = item.validate(value)
                elif item.values :
                        valid = value in item.values
                else :
                        try :
                                valid = item.type(value)
                        except ValueError :
                                valid = False
                if valid :
                        item.label.config(fg="black")
                else :
                        item.label.config(fg="red")
                        item.widget.focus_set()
                return valid
                
        def isvalid(self) :
                for item in self.items :
                        if not self.validate(item) : return False
                return True

        def apply(self) :
                if self.isvalid() :
                        for item in self.items :
                                try :
                                        value = item.type(item.widget.get())
                                except AttributeError :
                                        value = 
item.type(item.widget.cget("value"))
                                if value != item.get(self.obj) :
                                        setattr(self.obj,item.name,value)


widget_types = { types.StringType : Tix.LabelEntry, types.IntType : Tix.Control, types.FloatType : Tix.LabelEntry, }


if __name__ == "__main__" : class myClass : string = "hello world" float = 1. int = 0

        proxy = Edit(
                myClass,
                [
                        Attribute("string",type(str())),
                        Attribute("float",type(float())),
                        Attribute("int",type(int()),validate=lambda x : int(x) 
in [-1,0,1] )
                ]
        )

        win = Tix.Tk(None)
        proxy.create_widgets(win)
        proxy.update_widgets()

        button1 = Tkinter.Button(
                win,
                text="apply",
                command=proxy.apply
        )
        button1.pack()
        
        button2 = Tkinter.Button(
                win,
                text="update",
                command=proxy.update_widgets
        )
        button2.pack()
        
        win.mainloop()




-- Man will occasionally stumble over the truth, but most of the time he will pick himself up and continue on. -- Winston Churchill

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to