2

I'm trying to create of grid of widgets. This grid of widgets starts out as labels telling me their coordinates. I then have a list of starting and ending points for buttons that will replace them. Say I have a button that will go from (0, 0) to (0, 2), I remove the labels from this location and put a button there with the correct rowspan. If a button will be replacing another button (not just a label), I create a frame, then I want to clone the button as a way of changing the parent (which I've read is not a possibility with tkinter) and add the new button to the frame as well. The frame will then replace the widgets (labels and old buttons) on the grid with the buttons side by side instead of overlapping.

So this example image shows a grid of Labels, then where the first button is placed, then where the second button should go, and the resulting frame with both buttons in it side by side.

The big issue for me is having to remove the first button and re-place it on the grid because it's not possible to change the parent of a widget. Although I'm welcome to better ideas on getting buttons side by side on the grid as well.

itsknob
  • 33
  • 1
  • 6
  • You can't change the parent of the widget, but you can change which widget another widget is positioned inside. Will that solve your problem? It's really unclear exactly what your problem is. – Bryan Oakley Sep 30 '17 at 20:52
  • @Bryan Oakley How do you change which widget another widget is positioned inside of? Right now I'm cycling through my entire window's Frame.grid_slaves() (I'm storying this in a variable called info)and utilizing the information provided by that to recreate the button. When I find the correct info["row"] and info["column"] I'm creating a new button using this information, but this is only the information about how this button is placed on the Grid. Not the information about the button like it's text. – itsknob Sep 30 '17 at 20:58
  • _"How do you change which widget another widget is positioned inside of? "_ - the geometry managers all accept an `in_` parameter to specify where to put the widget. The rule, straight from the documentation: _"The master for each slave must either be the slave's parent (the default) or a descendant of the slave's parent."_ – Bryan Oakley Sep 30 '17 at 21:06
  • So if I create a Frame with the same parent as the Button I'm trying to clone, I can change the `in_` parameter of the button to be contained within the new Frame because it's parent is the parent of the Button as well? – itsknob Sep 30 '17 at 22:24
  • Try it and see. – Bryan Oakley Sep 30 '17 at 22:28

2 Answers2

8

There is no direct way to clone a widget, but tkinter gives you a way to determine the parent of a widget, the class of a widget, and all of the configuration values of a widget. This information is enough to create a duplicate.

It would look something like this:

def clone(widget):
    parent = widget.nametowidget(widget.winfo_parent())
    cls = widget.__class__

    clone = cls(parent)
    for key in widget.configure():
        clone.configure({key: widget.cget(key)})
    return clone
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
2

To expand the answer by Bryan Oakley, here a function that allows you to completely clone a widget, including all of its children:

def clone_widget(widget, master=None):
    """
    Create a cloned version o a widget

    Parameters
    ----------
    widget : tkinter widget
        tkinter widget that shall be cloned.
    master : tkinter widget, optional
        Master widget onto which cloned widget shall be placed. If None, same master of input widget will be used. The
        default is None.

    Returns
    -------
    cloned : tkinter widget
        Clone of input widget onto master widget.

    """
    # Get main info
    parent = master if master else widget.master
    cls = widget.__class__

    # Clone the widget configuration
    cfg = {key: widget.cget(key) for key in widget.configure()}
    cloned = cls(parent, **cfg)

    # Clone the widget's children
    for child in widget.winfo_children():
        child_cloned = clone_widget(child, master=cloned)
        if child.grid_info():
            grid_info = {k: v for k, v in child.grid_info().items() if k not in {'in'}}
            child_cloned.grid(**grid_info)
        elif child.place_info():
            place_info = {k: v for k, v in child.place_info().items() if k not in {'in'}}
            child_cloned.place(**place_info)
        else:
            pack_info = {k: v for k, v in child.pack_info().items() if k not in {'in'}}
            child_cloned.pack(**pack_info)

    return cloned

Example:

root = tk.Tk()

frame = tk.Frame(root, bg='blue', width=200, height=100)
frame.grid(row=0, column=0, pady=(0, 5))

lbl = tk.Label(frame, text='test text', bg='green')
lbl.place(x=10, y=15)

cloned_frame = clone_widget(frame)
cloned_frame.grid(row=1, column=0, pady=(5, 0))

root.mainloop()

Gives:

Example of cloned frame

ALai
  • 739
  • 9
  • 18