Tkinter custom window
Asked Answered
P

2

2

I want to make a window in Tk that has a custom titlebar and frame. I have seen many questions on this website dealing with this, but what I'm looking for is to actually render the frame using a canvas, and then to add the contents to the canvas. I cannot use a frame to do this, as the border is gradiented.

According to this website: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_window-method, I cannot put any other canvas items on top of a widget (using the create_window method), but I need to do so, as some of my widgets are rendered using a canvas.

Any suggestions on how to do this? I'm clueless here.

EDIT: Bryan Oakley confirmed that rendering with a canvas would be impossible. Would it then be possible to have a frame with a custom border color? And if so, could someone give a quick example? I'm sort of new with python.

Perique answered 9/8, 2012 at 21:53 Comment(5)
I don't understand the question, your edit makes it more unclear. Yes, it's possible to create a frame with a custom border, by using a canvas. That has nothing to do with drawing canvas items on top of widgets -- those are two distinct concerns.Baro
I thought there was a bordercolor attribute... This wouldn't work for a toplevel frame, right?Perique
I didn't confirm that "rendering with a canvas would be impossible". I simply confirmed that you can't draw canvas items on top of widgets embedded in a canvas. Certainly you can render a border and custom titlebar using the canvas.Baro
You mean using 4 canvases to draw the border?Perique
you can use as many canvases as you want. Just one for the whole background+border, or four, one for each side.Baro
B
3

You can use the canvas as if it were a frame in order to draw your own window borders. Like you said, however, you cannot draw canvas items on top of widgets embedded in a canvas; widgets always have the highest stacking order. There is no way around that, though it's not clear if you really need to do that or not.

Here's a quick and dirty example to show how to create a window with a gradient for a custom border. To keep the example short I didn't add any code to allow you to move or resize the window. Also, it uses a fixed color for the gradient.

import Tkinter as tk

class GradientFrame(tk.Canvas):
    '''A gradient frame which uses a canvas to draw the background'''
    def __init__(self, parent, borderwidth=1, relief="sunken"):
        tk.Canvas.__init__(self, parent, borderwidth=borderwidth, relief=relief)
        self._color1 = "red"
        self._color2 = "black"
        self.bind("<Configure>", self._draw_gradient)

    def _draw_gradient(self, event=None):
        '''Draw the gradient'''
        self.delete("gradient")
        width = self.winfo_width()
        height = self.winfo_height()
        limit = width
        (r1,g1,b1) = self.winfo_rgb(self._color1)
        (r2,g2,b2) = self.winfo_rgb(self._color2)
        r_ratio = float(r2-r1) / limit
        g_ratio = float(g2-g1) / limit
        b_ratio = float(b2-b1) / limit

        for i in range(limit):
            nr = int(r1 + (r_ratio * i))
            ng = int(g1 + (g_ratio * i))
            nb = int(b1 + (b_ratio * i))
            color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
            self.create_line(i,0,i,height, tags=("gradient",), fill=color)
        self.lower("gradient")

class SampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.wm_overrideredirect(True)
        gradient_frame = GradientFrame(self)
        gradient_frame.pack(side="top", fill="both", expand=True)
        inner_frame = tk.Frame(gradient_frame)
        inner_frame.pack(side="top", fill="both", expand=True, padx=8, pady=(16,8))

        b1 = tk.Button(inner_frame, text="Close",command=self.destroy)
        t1 = tk.Text(inner_frame, width=40, height=10)
        b1.pack(side="top")
        t1.pack(side="top", fill="both", expand=True)

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()
Baro answered 9/8, 2012 at 23:13 Comment(1)
Okay, thanks, I see now! Just one other question about this, how would I surround a frame with 4 canvases (1xH or Wx1)?Perique
C
1

Here is a rough example where the frame, titlebar and close button are made with canvas rectangles:

import Tkinter as tk

class Application(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        # Get rid of the os' titlebar and frame
        self.overrideredirect(True)

        self.mCan = tk.Canvas(self, height=768, width=768)
        self.mCan.pack()

        # Frame and close button
        self.lFrame = self.mCan.create_rectangle(0,0,9,769,
                                    outline='lightgrey', fill='lightgrey')
        self.rFrame = self.mCan.create_rectangle(760,0,769,769,
                                    outline='lightgrey', fill='lightgrey')
        self.bFrame = self.mCan.create_rectangle(0,760,769,769,
                                    outline='lightgrey', fill='lightgrey')
        self.titleBar = self.mCan.create_rectangle(0,0,769,20,
                                    outline='lightgrey', fill='lightgrey')
        self.closeButton = self.mCan.create_rectangle(750,4,760, 18,
                                            activefill='red', fill='darkgrey')

        # Binds
        self.bind('<1>', self.left_mouse)
        self.bind('<Escape>', self.close_win)

        # Center the window
        self.update_idletasks()
        xp = (self.winfo_screenwidth() / 2) - (self.winfo_width() / 2)
        yp = (self.winfo_screenheight() / 2) - (self.winfo_height() / 2)
        self.geometry('{0}x{1}+{2}+{3}'.format(self.winfo_width(),
                                                self.winfo_height(),
                                                            xp, yp))

    def left_mouse(self, event=None):
        obj = self.mCan.find_closest(event.x,event.y)
        if obj[0] == self.closeButton:
            self.destroy()

    def close_win(self, event=None):
        self.destroy()

app = Application()
app.mainloop()

If I were going to make a custom GUI frame I would consider creating it with images,
made with a program like Photoshop, instead of rendering canvas objects.
Images can be placed on a canvas like this:

self.ti = tk.PhotoImage(file='test.gif')
self.aImage = mCanvas.create_image(0,0, image=self.ti,anchor='nw')

More info →here←

Certainty answered 10/8, 2012 at 2:25 Comment(1)
thanks! I guess I'll have to figure out a way to make 4 canvases on each side (and a frame in the middle).Perique

© 2022 - 2024 — McMap. All rights reserved.