0

I was trying to fix something related to what an object returns when you use an object as an argument.

For example, I have a custom object and I want to use it as a parent for another tkinter widget, so I need to return a tkinter object to place the new tkinter object into my custom object, but my custom object returns that is an object of a custom class.

I can explain this much better on code:

class CustomFrame(object):
    def __init__(self,**args):
        #code that works

        #The next is an external object that have a variable that I need to call externally.
        #The variable is "interior"
        self.externalobject = anotherobject('random')

cfr1 = CustomFrame()

label1 = Label(cfr1)

Now I want to use the "self.externalobject.interior" as the parent for label1 But I want to make it friendly just calling "cfr1" instead of "self.externalobject.interior"

I know that if I use the call method and I return the value that I need it will work if I pass "cfr1" as a function, but I want to make it the more Pythonic as possible.

So I need to know if there's another special method or something to modify what it returns.

EDIT: So, this is part of the code I am using:

This is the code of the vertical scrolled frame (not my code).

class VerticalScrolledFrame(Frame):
    """A pure Tkinter scrollable frame that actually works!
    * Use the 'interior' attribute to place widgets inside the scrollable frame
    * Construct and pack/place/grid normally
    * This frame only allows vertical scrolling

    """
    def __init__(self, parent, bg, *args, **kw):
        Frame.__init__(self, parent, *args, **kw)            

        # create a canvas object and a vertical scrollbar for scrolling it
        vscrollbar = Scrollbar(self, orient=VERTICAL)
        canvas = Canvas(self, bd=0, highlightthickness=0,
                        yscrollcommand=vscrollbar.set,bg=bg)
        vscrollbar.config(command=canvas.yview)
        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)

        # reset the view
        canvas.xview_moveto(0)
        canvas.yview_moveto(0)

        # create a frame inside the canvas which will be scrolled with it
        self.interior = interior = Frame(canvas,bg=bg)
        interior_id = canvas.create_window(0, 0, window=interior,
                                           anchor=NW)

        a = Frame(self.interior,height=10,bg=dynamicBackground())
        a.pack()

        def canvasscroll(event):
            canvas.yview('scroll',int(-1*(event.delta/120)), "units")

        def _configure_canvas(event):
            a.configure(height=10)
            a.update()
            mylist = interior.winfo_children()
            for i in mylist:
                lasty=i.winfo_height()+i.winfo_y()
            a.configure(height=lasty)
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the inner frame's width to fill the canvas
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
            if canvas.winfo_height()<lasty:
                vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
                canvas.config(scrollregion=(0,0,0,lasty))
                canvas.bind_all("<MouseWheel>", canvasscroll)
            else:
                canvas.unbind_all("<MouseWheel>")
                try:
                    vscrollbar.pack_forget()
                except:
                    pass
                canvas.config(scrollregion=(0,0,0,0))


        canvas.bind('<Configure>', _configure_canvas)

And this is the code for my custom object:

class UtilityPanel(object):
    def __init__(self,parent='*',title='Main Title',state='normal',bg='red'):
        super().__init__()
        if parent != '*':
            self.parent=parent
        else:
            raise TypeError('You must specify a parent for this widget.')
        self.title=title

        global subpanels

        if len(subpanels) == 0:
            self.panelid = 'sp-1'
            subpanels.append('sp-1')
        else:
            self.panelid = 'sp-'+str(int(subpanels[-1].split('-')[1])+1)
            subpanels.append(self.panelid)

        self.panel = VerticalScrolledFrame(self.parent,bg=bg,width=600,height=200,name=self.panelid)

    def __call__(self,name):
        return(self.panel.interior)

    def pack(self):
        self.panel.place(x=300)
        global activepanel
        activepanel = self.panelid

So, if i pass the argument like label1 = Label(cfr1.panel.interior) it works, but I want it to make it work just passing cfr1 as an argument.

  • I think there's a good question in here, but it's hard to read, partly because you've put some of it as comments in the code. (I didn't downvote BTW). – doctorlove Jan 09 '19 at 12:49
  • I made changes to make it better to read, just want to explain every part of the "uncompleted" code that I post – David González Blazman Jan 09 '19 at 12:53
  • 2
    The only thing that can be the parent of a Tkinter widget is another Tkinter widget. For your custom object to fulfill that role, it needs to derive from a Tkinter class - I suppose `Frame` in your case. – jasonharper Jan 09 '19 at 13:21
  • Yes, this is it, I need a Frame, self.externalobject.interior is a Frame, but I need to return it just calling cfr1. – David González Blazman Jan 09 '19 at 13:22
  • What do you mean by "I need to return it"... I am not sure I understand your need here. That said I did notice you are missing `super().__init__()` under your `__init__` method and you need to inherit from frame. – Mike - SMT Jan 09 '19 at 13:23
  • What about doing this: `label1 = Label(cfr1.externalobject)`? Is this what you are trying to accomplish? – Mike - SMT Jan 09 '19 at 13:50
  • Yes, with `label1 = Label(cfr1.externalobject.interior)` works. – David González Blazman Jan 09 '19 at 13:52
  • But it's what I want to avoid. – David González Blazman Jan 09 '19 at 13:52
  • 1
    Ahhhh ok. Can you tell me what `interior` is exactly. Is it another container or just some variable holding a value? – Mike - SMT Jan 09 '19 at 13:53
  • I have edited the main question to show how it works. – David González Blazman Jan 09 '19 at 13:56
  • 1
    I see you are using the code from [this post](https://stackoverflow.com/questions/16188420/python-tkinter-scrollbar-for-frame). I am not sure if there is a way to link to the `interior` frame without doing `cfr1.externalobject.interior`. If there is all the testing I have done has failed and I am just unaware of the correct method to do this. Good luck. I hope someone knows the solution if there is one. I am curious myself to see if there is a way to do this. – Mike - SMT Jan 09 '19 at 14:16
  • 1
    If you want to use this custom object as a parent to another widget, why aren't you inheriting from a widget such as Frame? That makes this problem trivial. Anything else will result in confusing code that others will find hard to understand. – Bryan Oakley Jan 09 '19 at 14:58
  • Just because I want to use a property of a custom object created by a custom object. Not a single custom object instead. So, the same way, with `__repr__`, that it's the representation of the object when you show it, and it's editable, I need a method similar, but for when you call the object, so, instead of calling the object and return the type of the object, return whatever I want. – David González Blazman Jan 10 '19 at 07:37

3 Answers3

3

I think I understand the issue the OP is having here. They are unable to use the frame as the container for their label and that is because they are missing super() and need to inherit from Frame.

Change this:

class CustomFrame(object):
    def __init__(self,**args):

To this:

class CustomFrame(Frame):
    def __init__(self,**args):
        super().__init__()

And you should be able to use the customer frame as the container for your label.

Based on your comments below try this:

class CustomFrame(anotherobject):
    def __init__(self,**args):
        super().__init__()

This should inherit all methods and attributes of that object.

Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • Ok, I think I haven't explained correctly, but this is close to what I want. I explain a little more, I have an external custom object that is a scrollable frame for tkinter, and I have created a new object to give it new properties and act as a new object. This custom scrollable frame have the attribute interior, which is what I need to pass to a widget as a parent to fit in it. But I need to do make it like any widget in tkinter – David González Blazman Jan 09 '19 at 13:39
  • So, I need to, return this interior attribute of the scrollable frame, that is inside my new custom object, just passing the name of the custom object... Sorry, if I still don't explaining well, but it's difficult to me due to the language. – David González Blazman Jan 09 '19 at 13:40
  • Then instead of inheriting from `Frame` you can inherit from your customer object. That should work here. – Mike - SMT Jan 09 '19 at 13:43
  • It is a little unclear what you want. It almost sounds like you want to be able to use `CustomFrame()` as a way to pass another object as the container. So say `CustomFrame` inherits from `OtherObject` and you want to do `Label(CustomeFrame)` resulting in basically `Label(OtherObject)`. Is that correct? – Mike - SMT Jan 09 '19 at 13:48
  • I know what you're saying, but doesn't work because I define the scrollable frame into the custom object, so I can't inherit the property from an object that is created inside. – David González Blazman Jan 09 '19 at 13:49
  • Ok, wait a moment, I will edit the main post with the code, I think this could give a clearer idea of what I want. – David González Blazman Jan 09 '19 at 13:49
0

You are trying to implicitly cast/coerce one type of object to another.

If you want to be "pythonic", recall,

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
...

Implicitly doing things can cause trouble.

I see nothing wrong with using the call magic function to get the attribute you want, or start looking at properties and decorators.

There's a full list of magic methods here

doctorlove
  • 18,872
  • 2
  • 46
  • 62
0

You could probably achieve what you want if you created your own custom subclass of tk.Label that basically does the unpacking for you.

(Disclaimer: I have never used tkinter, so I cannot tell if this actually works in the context of that framework. This example is merely to demonstrate the concept I am trying to outline here.)

class CustomLabel(tk.Label):

    def __init__(self, master, **options):
        if isinstance(master, CustomFrame):
            master = master.externalobject.interior
        super().__init__(master=master, **options)

With that you should be able to use CustomLabel(cfr1).

However, I strongly support the message that @doctorlove conveys in their answer. IMHO, the most pythonic way would indeed be Label(cfr1.externalobject.interior), your suggested approach using __call__ or a property inside CustomFrame that provides a shortcut to externalobject.interior:

@property
def interior(self):
    return self.externalobject.interior

which you would use like so Label(crf1.interior)

shmee
  • 4,721
  • 2
  • 18
  • 27