Tkinter can't lift item within canvas - python

I have 4 items in my canvas (canvas12). I'm trying to place the 2 drag buttons (frame1_drag/frame2_drag) in front of the windows (frame2can/frame1can). But I can't get tag_raise() or lift() to do anything. Right now the drag buttons are stuck behind the windows. And yes, it has to be this way because the drag buttons animate their respective framexcan window within canvas12.
frame2can = canvas12.create_window(0, 0, anchor='nw', width=sf(768), height=sf(fr1h),window=frame2)
frame1can = canvas12.create_window(0, sf(fr1h), anchor='nw', width=sf(768), height=sf(fr1h),window=frame1)
# Drag Buttons
frame1_drag = canvas12.create_rectangle(0, 0, sf(fr0h), sf(fr0h), outline="blue", fill="green", tags="token")
frame2_drag = canvas12.create_rectangle(0, sf(fr1h), sf(fr0h), sf(fr0h+fr1h), outline="blue", fill="green", tags="token")
canvas12.tag_raise(frame1_drag)
canvas12.tag_raise(frame2_drag)

You cannot place drawn items on top of window objects.
From the official tk documentation:
The items in a canvas are ordered for purposes of display, with the first item in the display list being displayed first, followed by the next item in the list, and so on. Items later in the display list obscure those that are earlier in the display list and are sometimes referred to as being “on top” of earlier items. When a new item is created it is placed at the end of the display list, on top of everything else. Widget commands may be used to re-arrange the order of the display list.
Window items are an exception to the above rules. The underlying window systems require them always to be drawn on top of other items. In addition, the stacking order of window items is not affected by any of the canvas widget commands; you must use the raise and lower Tk commands instead.

Related

How can I set the order of duplicated widgets in tkinter, python?

I use image(image1) for background and made Entry with create_window method.
And I want to make image(image2) cover the entry with create_image method.
But the Entry widget is revealed everytime.(Entry - image2 - imgae1)
How can I change the order of these like image2- Entry - image1.
My code is like below.
self.img_main = PhotoImage(file=r"C:\Bart\My library\Python\Grinding\GUI\capture\image1.png")
self.canvas_main = Canvas(self, width=1366, height=768, bd=0)
self.canvas_main.pack(fill=BOTH, expand=True)
self.canvas_main.create_image(0,0, image=self.img_main, anchor=NW)
self.entry = Entry(self, width=12, justify="center", bg="#A5C1DE")
self.canvas_main.create_window(72, 287, height=32, anchor=NW, window=self.entry)
self.cbb_pro_sel_img_1 = PhotoImage(file=r"C:\Bart\My library\Python\Grinding\GUI\capture\image2.png")
self.cbb_pro_sel = self.canvas_main.create_image(36, 75, anchor=NW, image=self.cbb_pro_sel_img_1)
You cannot raise an image object on a canvas above an embedded widget. From the canvas documentation:
The items in a canvas are ordered for purposes of display, with the first item in the display list being displayed first, followed by the next item in the list, and so on. Items later in the display list obscure those that are earlier in the display list and are sometimes referred to as being “on top” of earlier items. When a new item is created it is placed at the end of the display list, on top of everything else. Widget commands may be used to re-arrange the order of the display list. Window items are an exception to the above rules. The underlying window systems require them always to be drawn on top of other items.
Instead, you can create a label that contains the image and then use create_window to put the label over the entry. Since it is a widget, it can appear above the entry widget.

PyQt make QFrame appear on top of another widget

So my application's main part consists of a QFrame which contains 2 other QFrames (left one for a menu, right for the content of the current page selected) I made the layout in QtDesigner as follows:
I added an animation for opening/closing the menu on the left, making the QPushButton texts visible when open, hiding them when closed (Only the icon shows in that case) by increasing the QFrame's width.
def ToggleMenu(self):
width = self.ui.frame_left_menu.width()
defaultWidth = 70
toggleWidth = 200
if width == defaultWidth:
toggleWidth = 200
self.ui.frame_content_right.lower()
self.ui.frame_left_menu.raise_()
else:
toggleWidth = 70
self.ui.frame_left_menu.lower()
self.ui.frame_content_right.raise_()
self.animation = QtCore.QPropertyAnimation(self.ui.frame_left_menu, b"minimumWidth")
self.animation.setDuration(300)
self.animation.setStartValue(width)
self.animation.setEndValue(toggleWidth)
self.animation.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
self.animation.start()
ToggleMenu() is called when the user clicks on the menu button (thats outside of frame_center, increasing/decreasing its width.
When opening the menu, its width is increased, thus pushing the right QFrame along, making the available space smaller for actual content.
I would like to be able to make it so the menu is displayed above the content QFrame when opened. I tried calling raise_() and lower() on open/close, but that doesn't seem to change anything, I think that is because they are not overlapping to begin with, given the layout above.
I would like to avoid creating the layout at runtime if possible. How could I solve this?

Tkinter box issues- Entry, Labelbox, Text all take up space from side to side, grid crashes computer

I'm using a tkinter canvas and trying to make a chat box on the right side of my game. However, I found that when I do...
import turtle
import tkinter as tk
master = tk.Tk()
w = tk.Canvas(master,width=1155,height=600,cursor='cross_reverse', bg='#101010')
shift = 1.000
sc = turtle.TurtleScreen(w)
tu = turtle.RawTurtle(sc)
e = tk.Entry(master, bg = '#000', fg = '#03f', font = 'Courier', justify='right', insertbackground = '#101010',width='115')
lb = tk.Listbox(master,height=3)
#e.grid(row=3,column=3)
sc.bgcolor("#101010")
txt = tk.Text(master,state="disabled")
txt.pack()
lb.pack()
w.pack()
sc.tracer(100)
drawcontinents() #Draws stuff with turtle, works just fine
e.pack()
tk.mainloop()
... a few things go wrong.
1.Text and Entry do not seem to want to coexist. I seem to be only able to have one or the other. My plan was to use entry as a chat entry, and display messages in Text. My backup plan is to append messages to label.
2.Text, entry, and Label box take up the entire window in whatever rows they are in, which blocks out the rest of what I am trying to draw. In other words,it puts the text box in the center, with a big gray stripe from side to side across whatever I've drawn. Is there any way to just display the box, and put it to the right?
3.Whenever I try to use the grid system, my whole computer freezes and I have to restart. Is this because the program is taking up more space than I have available, or is this a known bug or problem with installation?
You cannot use both pack and grid at the same time for the same containing widget (ie: for all widgets inside the same frame, toplevel or root window).
What happens is this: grid lays out all the widgets, potentially changing the size of some widgets based on your options (ie: it may grow a widget to stick to the sides of the cell). pack then notices that some widgets changed size in the containing widget it thinks it is responsible for, so it redoes what it thinks is the proper layout. This may change the size of some widgets based on your options. grid then notices that some widgets it thinks it is responsible for change size so it redoes what it does, potentially changing the size of some widgets. pack notices and re-adjusts, grid notices and re-adjusts, pack notices, ... until the end of time.
The solution is simple: only use grid, or only use pack, for all widgets that have a common parent. In this case, all your widgets share the root window as their parent, so they all need to use grid, or they all need to use pack.

Randomly add buttons to Tkinter GUI?

How do I randomly add buttons to a Tkinter GUI? I need it to be able to create a button, then put it anywhere on the window, is this possible? I am using Python 2.6 on Windows.
If you want random button placement (or anything not aligned along a grid, etc.), you can use the place geometry manager. Depending on platform, overlapped buttons may not behave as you expect, though, so you may want to avoid them.
Here's a simple example:
from Tkinter import *
from random import random
root = Tk()
frame = Frame(root, height=200, width=200)
for i in range(10):
Button(frame, text=str(i)).place(x=random() * 150, y=random() * 180)
frame.pack()
root.mainloop()
There are several options to choose from. For example, you could design on a grid where you have six buttons per row. Then it's just a matter of starting at row 0, incrementing the column for each button. When you get to the last column, reset the column to 0 and increment the row by one.
Another option is to use a text widget as the container, and embed your buttons in the text widget with wrapping enabled. With this trick the buttons will fill a row automatically and wrap if the user grows or shrinks the main windows. It's a tiny bit more work, but it works well if that's the behavior you want.

How to pack a tkinter widget underneath an existing widget that has been packed to the left side?

I'm attempting to write a basic Tkinter GUI that has a Text widget at the top, then a Button widget left aligned under it, then another Text widget underneath the button. The problem I'm having is, after packing the Button widget to the left, when I then go to pack the second Text widget, it puts it next to the button on the right, rather than underneath the button. This happens regardless of what I set the side argument to for the second Text widget Here's a simple piece of code that demonstrates this behaviour:
from Tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(side=LEFT)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
So how would I go about setting up the second Text widget so that it appears below the button, rather than to the right of it?
There are generally two solutions to layout problems:
switch to using grid. It becomes real easy to do layouts like what you are trying to accomplish. Grid can solve probably 95% of all layout issues (it's amazing when you think about it -- Tk does with one manager what most toolkits need half a dozen to accomplish!)
use multiple frames. If some widgets need to be stacked top-to-bottom and some left-to-right you can't always get what you want packing everything in a single frame. Use one frame for the top-to-bottom parts of the layout and additional frames for the left-to-right content.
Also realize that widgets don't have to be children of the widget in which they are packed/gridded. You can use the "in" parameter to put widgets in some other container than their parent.
For example, in your specific example you can create three frames, top, middle, bottom. Pack these top-to-bottom in your toplevel window. Then you can pack the first text widget in the top, the button or buttons horizontally in the middle, and the other text widget in the bottom.
The advantage to such an approach is that it makes it much easier to change the layout in the future (which in my experience always happens at some point). You don't have to re-parent any of your widgets, just pack/place/grid them in some other container.
In your short example it doesn't make much difference, but for complex apps this strategy can be a life saver.
My best advice is this: layout isn't an afterthought. Do a little planning, maybe even spend five minutes drawing on some graph paper. First decide on the major regions of your app and use a frame or some other container for each (paned window, notebook, etc). Once you have those, do the same divide-and-conquer approach for each section. This lets you use different types of layout for different sections of your app. Toolbars get horizontal layout, forms might get vertical layout, etc.
I was initially misunderstanding how packing worked and didn't realise that the entire left side was being "claimed" when i did x.pack(side=LEFT). What I found after reading this and the answer by Alex here is that I was not really after having x packed to the left side at all, but rather having it anchored to the left, using anchor=W (W for West) instead of side=LEFT. My revised code snippet which does what I was after looks like this:
from tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(anchor=W)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
This way x is not "claiming" the left side anymore, it's just aligned to the left (or West) within its block of space.
Packing happens in the order the .pack methods are called, so once x has "claimed" the left side, that's it -- it will take up the left portion of its parent and everything else within its parent will be to its right. You need a Frame to "mediate", e.g....:
from Tkinter import *
root = Tk()
w = Button(root, text="Mysterious W")
w.pack()
f = Frame(root)
x = Button(f, text="Hi there!")
x.pack()
y = Button(f, text="I be Y")
y.pack(side=BOTTOM)
f.pack(side=LEFT)
root.mainloop()
(changed Texts to Buttons for more immediate visibility of layout only -- the Tkinter on this Mac doesn't show Texts clearly until they have focus, but Buttons are quite clear;-).
Do it the same way that WebView does using the Mosaic Canvas Widget Sets internals(which are very similar to Tk). The trick is that the second identical named Frame Object works as a Block Level Float(inline:block;) for everything placed after it and everything that calls "fr" already will automatically begin over inside of it.
You can have many doing this of TOP aligned widgets and simply add another identical named Frame where you want to break between side=LEFT's. Works after Bottom also.
fr=Frame(root)
fr.pack(fill=X, side=TOP)
block1=Label(fr)
block1.pack(side=LEFT)
block2=Label(fr)
block2.pack(side=LEFT)
block3=Button(fr)
block3.pack(side=LEFT)
# NAME IT THE SAME ID NAME AS THE FIRST MAIN FRAME...
fr=Frame(root)
fr.pack(fill=X, side=TOP)
# These NOW jump into the second Frame breaking the side=LEFT in new Frame
block4=Label(fr)
block4.pack(side=LEFT)
block5=Label(fr)
block5.pack(side=LEFT)
# AND THEY CONTINUE GOING side=LEFT AFTERWARDS.

Categories

Resources