4

I have a frame and I want to make a Toplevel window from it. I want to make a system similar to how the web browser UI works. For example, in Google Chrome you can take a tab and make a new window from it. You can also add other tabs to that new window. That is what I have tried to "demonstrate" this behavior:

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
moving_frame = tk.Frame()
moving_frame.pack()

notebook = ttk.Notebook(moving_frame, width=400, height=700)
notebook.pack()

movingFrame = tk.Frame(notebook, bg='green', width=400, height=700)
movingFrame2 = tk.Frame(notebook, bg='green', width=400, height=700)

lab = tk.Label(movingFrame, text='some text')
ent = tk.Entry(movingFrame)

ent2 = tk.Entry(movingFrame2, width=10, bg='grey30')

lab.pack()
ent.pack()
ent2.pack()

notebook.add(movingFrame, sticky='nesw', text='tab1')
notebook.add(movingFrame2, sticky='nesw', text='tab2')

def window_create(e):
    if notebook.identify(e.x, e.y):
        print('tab_pressed')
        frame_to_window = root.nametowidget(notebook.select())
        root.wm_manage(frame_to_window)

notebook.bind('<ButtonRelease-1>', window_create)
root.mainloop()

Note: This code works by pressing the tab, not by dragging it.

I wonder if I can somehow adjust the window that is created by using wm_manage. This function return None and works not as I thought, but close enough to what I need.

If that is not possible to make by using wm_manage, how should I do that?

I thought I can create a custom class, for example from a Frame, but there is a problem: new window should be the same to the frame that it is created from. If something has been changed by a user, e.g., User added a text to the Entry; Marked some checkbuttons; If he has used a Treeview hierarchy, like this:

, the new window should remember which "folders" are opened (that are marked as minus on the picture), which items are currently selected and so on. And that was only about the Treeview, similar things should be considered for every single widget. Quite a lot of work, so maybe there is a better way to do that.

D_00
  • 1,440
  • 2
  • 13
  • 32
Danya K
  • 165
  • 1
  • 10
  • *Close enough to what I need* -- What is it, that you need and how does it differ from `wm_manage`? – Thingamabobs Aug 26 '23 at 13:26
  • @Thingamabobs `wm_manage` doesn't allow me to adjust that new window. I want, for example, use `overrideredirect` on it or use `geometry`, but it all seems impossible to do. – Danya K Aug 26 '23 at 13:38
  • It should be possible. However what do you plan to do with that window? Because you won't be able to drag and drop the widget into a different window if that's the plan. is it? – Thingamabobs Aug 26 '23 at 13:55
  • @Thingamabobs It should be, but tkinter creates that window in a very strange way, like it is a window and a frame at the same time. I plan to create a UI system similar to how any browsers work, where you can create a new window just by dragging the current tab, add more tabs to the new window and so on. – Danya K Aug 26 '23 at 14:07
  • 1
    Look, you can use `root.call('wm', 'geometry', frame_to_window, "500x500")` for example. So tcl code works for that matter and it should be not too hard to write a wrapper for it. Also it should be possible with some work to rearrange the content of that frame into new Notebook frame in your new *window*. But still, you won't be able to move a tab from one window into another. – Thingamabobs Aug 26 '23 at 14:27
  • In case you wonder why [here](https://stackoverflow.com/a/18161472/13629335) is a perfectly fine explained answer to that. – Thingamabobs Aug 26 '23 at 14:38
  • @Thingamabobs Thank you **very** much for the `root.call`. I never thought that this would work, even though I knew that `call` and `eval` exist. Now I am 10 times happier. Even `overrideredirect` works now! About *you won't be able to move a tab from one window into another* I will try to test it by myself first to see, why you think it will not work, because I don't see any problem with it right now. – Danya K Aug 26 '23 at 14:57
  • Well as long as you won't make it work by yourself, by reconstructing the widget for instance and put a lot of effort in it, tkinter won't do it for you as the linked answer suggests. Also I do doubt that it will be fast when it gets to complex widget trees. – Thingamabobs Aug 26 '23 at 15:10

1 Answers1

1

In conclusion, I want to say that I have not yet been able to make a similar to web browser UI system. A lot of problems came up during my attempts. Maybe I will do that in the future and edit this answer.

But my main question was how to adjust the new window created by wm_manage? One of the solutions came from the comments, but I found a better way to do that, as simple as only possible:

class ToplevelFrame(tk.Frame, tk.Toplevel):
    pass

This is a class, that normally acts just as tk.frame, but after the wm_manage command it will be possible to use standard Toplevel methods, such as overrideredirect, geometry, resizable and so on. It is also possible to add a Menu to it if needed. Here is a quick example of what can be done:

import tkinter as tk


class ToplevelFrame(tk.Frame, tk.Toplevel):
    pass


if __name__ == "__main__":
    root = tk.Tk()

    top_frame = ToplevelFrame(root, bg='grey20')
    top_frame.pack()

    text_wid = tk.Text(top_frame, width=150, height=40)
    text_wid.pack(padx=40, pady=40)

    def transform():
        if button["text"] == 'to window':
            root.wm_manage(top_frame)
            button['text'] = 'to frame'

            # examples
            top_frame.wm_overrideredirect(True)
            top_frame.geometry('+600+100')
            top_frame.resizable()

            mainmenu = tk.Menu(top_frame)
            top_frame['menu'] = mainmenu

            filemenu = tk.Menu(mainmenu, tearoff=0)
            filemenu.add_command(label="Open...")
            filemenu.add_command(label="New")
            filemenu.add_command(label="Save as...")
            filemenu.add_command(label="Quit")

            mainmenu.add_cascade(label="File", menu=filemenu)

        else:
            root.wm_forget(top_frame)
            button['text'] = 'to window'
            top_frame.pack(side='bottom')


    button = tk.Button(text='to window', command=transform)
    button.pack(fill='both', expand=True, side='bottom')
    root.mainloop()

Note: Attempting to use Toplevel methods when ToplevelFrame is not yet a stand-alone window will result in an error: _tkinter.TclError: window ".!toplevelframe" isn't a top-level window

Danya K
  • 165
  • 1
  • 10
  • Is this the answer to your question? Does it work as expected? Also a naive approach would be to just get the contents of `text_wid` and create a new `tk.Toplevel` with `tk.Text` and use `.insert("end", )` to copy everything into the new text widget. Assuming that you don't have a lot of widgets, that might be the simplest approach. – TheLizzard Sep 01 '23 at 19:25
  • @TheLizzard not really. I didn't complete the the main task that I wanted. I know that it is possible to make a copy of the widget, but it is hard and not quite good in my opinion (I wrote more about that in a top question). Copying works for a very simple task. Imagine a `Text` widget used `StringVar`. And it is also used in another widget. This will also make everything more complex. – Danya K Sep 01 '23 at 22:46