GridLayout+ScrollArea widget position after size change - pyqt

I have a scrollArea with a gridlayout inside it, and i add QLabels to it with images. When the application starts it works fine and displays the labels correctly:
Note: i calculate how many labels fit on the current layout space.
If i maximize it works fine too:
But when i hit restore something weird happens:
You can see that only 6 labels are added (the same as in the first Screen shot) but here they are all positioned overlapping each other.
This is the initialization code for the ScrollArea and the Layout:
self.scrollArea = QtGui.QScrollArea(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Ignored)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth())
self.scrollArea.setSizePolicy(sizePolicy)
self.scrollArea.setAutoFillBackground(True)
self.scrollArea.setStyleSheet(_fromUtf8("border: 1px solid blue"))
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName(_fromUtf8("scrollArea"))
self.gridLayoutWidget = QtGui.QWidget()
self.gridLayoutWidget.setGeometry(QtCore.QRect(0, 0, 667, 551))
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.gridLayoutWidget.sizePolicy().hasHeightForWidth())
self.gridLayoutWidget.setSizePolicy(sizePolicy)
self.gridLayoutWidget.setLayoutDirection(QtCore.Qt.LeftToRight)
self.gridLayoutWidget.setAutoFillBackground(True)
self.gridLayoutWidget.setStyleSheet(_fromUtf8("border: 1px solid red"))
self.gridLayoutWidget.setObjectName(_fromUtf8("gridLayoutWidget"))
self.scrollArea.setWidget(self.gridLayoutWidget)

So thanks to #pyqt on freenode (shout out to Avaris) i now know what the problem is. It seems to be a bug in QGridLayout.
When we maximize the window QGridLayout ends up with 12 columns, when we do a restore even though every item is removed the layout still assumes 12 columns, so in picture 3 it is displaying 6 images but thinks it needs to display 12 so it just overlaps the other ones.

Related

Is there a way to remove the checkbutton border in tkinter?

Is there any way to remove the border around a checkbutton? I have tried relief = "flat", offrelief = "flat", borderwidth = 0 and highlightthickness = 0 and none of these have made any difference. I also found This question, but the first answer doesn't work, and the second involves recreating the widget, but I wish to use the Checkbutton widget. The reason I need to remove the border is that I'm wrapping the button in a frame to change it's border, and If I can't remove the default border, it isn't possible.
This is the border I mean:

How to set graphics for a push button using Qt Designer?

I created a pushButton with a border image using Qt Designer. The stylesheet of the pushButton looks like the following.
border-image: transparent;
border-image: url(:/button_img/main_button.png);
The button looks like as follows:
The button works as expected but I do not know how can I set border inset or onset for that button. My terminologies might be wrong because I am very new to GUI development. What I am looking for is when you creata a normal pushButton, you can visualy see when you click that button. But in my case the button works but I cannot see the graphics when I click the button.
How can I add the border inset or onset? Please let me know in case of additional information.
No, you can't; not like that.
As the name hints, the border-image CSS property is used to draw borders; while what you see seems like a "background image", it actually is a border object that splits your image in a 3x3 grid rectangle that covers the whole button area; each section is then "stretched" to fill its own rectangle.
+-----------+---------+------------+
| top left | top | top right |
+-----------+---------+------------+
| left | center | right |
+-----------+---------+------------+
|bottom left| bottom |bottom right|
+-----------+---------+------------+
To understand how all of this works, read the "Common Mistakes" section of the stylesheet examples documentation. Here's what actually happens (I'm using half-width/half-height margins for the sake of the argument, so only the 4 "angle" rectangles are shown, so the left, top, right, bottom and center rectangles will be null):
If you want to use style sheets that interactively show borders, the standard approach is have 2 different images for the button state, and both of them needs their border. If you also want to achieve the "hover" effect, you'll need a third image that will have no border at all.
The downside of this method is that if the icon is resized, the border will be resized too.
The only alternative I can think of is to subclass your own QPushButton widget and override its paintEvent method. You could also use an event filter, but that wouldn't give you any major benefit, as it would use a very similar approach anyway, while complicating things if the object tree is even slightly complex.
In the following example I use an implementation that is based on stylesheets only: Qt can use the qproperty-* stylesheet parameter to set existing properties of a class (Qt properties that are already part of the class, or that are added in the class constructor: using setProperty on an unexisting property within the __init__ method will not work).
While this kind of implementation is usually not required (you could set the pixmap attribute of the class instance by hand) the advantage is that with this approach you can set everything within the stylesheet only.
class PixmapButton(QtWidgets.QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._pixmap = QtGui.QPixmap()
self.setCheckable(True)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
#QtCore.pyqtProperty(QtGui.QPixmap)
def pixmap(self):
return self._pixmap
#pixmap.setter
def pixmap(self, pixmapPath):
self._pixmap = QtGui.QPixmap(pixmapPath)
def paintEvent(self, event):
opt = QtWidgets.QStyleOptionButton()
self.initStyleOption(opt)
qp = QtGui.QPainter(self)
# draw the basic button
self.style().drawControl(QtWidgets.QStyle.CE_PushButton, opt, qp, self)
if self._pixmap.isNull():
return
# get the minimum possible size for the pixmap icon
minSize = min(self.width(), self.height())
# remove the margin/padding usually necessary for the button contents
minSize -= self.style().pixelMetric(QtWidgets.QStyle.PM_ButtonMargin, opt, self) * 2
# create a rectangle based on the minimal size and move it to the center
# of the button
rect = QtCore.QRect(0, 0, minSize, minSize)
rect.moveCenter(self.rect().center())
# finally, draw the "icon"
qp.drawPixmap(rect, self._pixmap.scaled(rect.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
Here's a test window that will show the border-image implementation on top (along with its border splitting), and the subclass painting at the bottom.
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
layout.addWidget(QtWidgets.QLabel('basic border-image'), 0, 0, QtCore.Qt.AlignCenter)
borderButton = QtWidgets.QPushButton()
layout.addWidget(borderButton, 1, 0, QtCore.Qt.AlignCenter)
borderButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
borderButton.setFixedSize(120, 120)
borderButton.setStyleSheet('''
border-image: url(play.png);
''')
layout.addItem(QtWidgets.QSpacerItem(120, 0, QtWidgets.QSizePolicy.Expanding))
layout.addWidget(QtWidgets.QLabel('expanded border-image'), 0, 1, QtCore.Qt.AlignCenter)
borderButtonExpanded = QtWidgets.QPushButton()
layout.addWidget(borderButtonExpanded, 1, 1)
borderButtonExpanded.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
borderButtonExpanded.setStyleSheet('''
border-image: url(play.png) 60 60 60 60;
border-top: 60px transparent;
border-bottom: 60px transparent;
border-right: 60px transparent;
border-left: 60px transparent;
''')
layout.addItem(QtWidgets.QSpacerItem(0, 10))
layout.addWidget(QtWidgets.QLabel('paintEvent implementation'), 2, 0, 1, 2, QtCore.Qt.AlignCenter)
pmButton = PixmapButton()
layout.addWidget(pmButton, 3, 0, 1, 2)
pmButton.setMinimumSize(120, 120)
pmButton.setStyleSheet('''
QPushButton {
qproperty-pixmap: url(play.png);
/* hover mode: while there's no border shown, we can set the
default radius for the hover and pressed statuses */
border: none;
border-radius: 4px;
}
/* show border only if hovered or is checkable */
QPushButton:hover, QPushButton[checkable="true"] {
border: 1px outset green;
}
QPushButton:pressed, QPushButton:checked {
border: 1px inset green;
}
''')
pmButton2 = PixmapButton()
layout.addWidget(pmButton2, 4, 0, 1, 2)
pmButton2.setCheckable(True)
pmButton2.setMinimumSize(120, 120)
pmButton2.setStyleSheet(pmButton.styleSheet())
Note: This is a very simplified implementation. It does not check if the user sets a custom icon for the button, and there should be no button text at all.

How to add a scrollbar to an overlapping canvas in Python tkinter

I am trying to make an application for a school project, one of the features is a messaging service where users can message each other, this is accessed via a button in which the messaging GUI is loaded. I have already added a canvas as the background for the main GUI at the start of the program but for the message interface I have added another canvas which overlaps and will be using a scrollbar to scroll through the messages and display them.
In essence my problem is that I would like to position another canvas on top of the main canvas using co-ordinates and add a scrollbar which only fits to the right hand side of this smaller canvas.
Thanks in advance :)
I have tried to add a scrollbar to it using pack(), grid(), place() and canvas.create_window() however in all instances the scrollbar either does not appear, does not fill the entire right hand side of the second canvas, or is not placed in the correct position. The closest I got was with the place() function where I have managed to position the scrollbar and canvas using "relx", "rely" and "relheight" however the scrollbar is not scrolling the canvas.
root = Tk() #creates the GUI
root.title("Eclass")
root.iconbitmap(default='icon.ico') #sets icon
root.state('zoomed')
backgroundcolour = root.cget("bg")
screen_width = root.winfo_screenwidth() - 15
screen_height = root.winfo_screenheight()
canvas = Canvas(root, width = screen_width, height = screen_height)
def messaging():
canvas.delete("all")
msg_canvas = Canvas(canvas, width = 1530, height = 730, bg = "red")
#canvas.create_window(1123,600, window = msg_canvas) this is where I tried to add the second canvas using the create_Window function
msg_canvas.place(relx=0.186, rely=0.227)
msg_scrollbar = Scrollbar(canvas, orient="vertical",command=msg_canvas.yview)
msg_scrollbar.place(relx=0.975,rely=0.2295, relheight = 0.717)
msg_canvas.config(scrollregion=msg_canvas.bbox(ALL))
I expect the canvas to be placed within the current co-ordinates given via relx and rely or in previous trial the co-ordinates in canvas.create_window(), I then expect the msg_scrollbar to be on the right hand side of msg_canvas and fill to its Y (the height of the scrollbar should be the same as the height of the canvas). In actuality the canvas and scrollbar are in the correct co-ordinates however the scrollbar does not scroll the msg_canvas even after moving it.
, In essence, my problem is that I would like to position another canvas on top of the main canvas using co-ordinates and add a scrollbar which only fits the right-hand side of this smaller canvas.
I recommend against using place. Both pack and grid are better for creating code that is responsive to changes in resolution, window size, and fonts.
IMHO, the simplest solution is to use pack to put the main canvas at the bottom, then in the remaining space put the scrollbar on the right and the secondary canvas on the left. Note: the order is important, as each time you call pack it will use up all of the space on the given side, reducing the space available to future widgets.
canvas.pack(side="bottom", fill="both", expand=True)
msg_scrollbar.pack(side="right", fill="y")
msg_canvas.pack(side="left", fill="both", expand=True)
You can accomplish the same thing with grid, but it requires additional lines of code to configure row and column weights. With grid order isn't as important since you are explicitly setting rows and columns.
msg_canvas.grid(row=0, column=0, sticky="nsew")
msg_scrollbar.grid(row=0, column=1, sticky="ns")
canvas.grid(row=1, column=0, columnspan=2, sticky="nsew")
root.grid_rowconfigure((0,1), weight=1)
root.grid_columnconfigure(0, weight=1)

python tkinter, Scrollbar missing knob(thumb)

【Summary】
By tkinter, python. I set scrollbar on Canvas ... Then that has been succeeded.
But missing knob in scrollbar.
【Background】
This is my application that developing now.
App's purpose is simple. Get some icons from target URL, Then put as tile in Window.
Application Window graphic
As you see, Can't put all icons in initial window size.
So I want to use scrollbar, Then scrolldown to show below icons.
Now succeeded put scrollbar at right side. But in that bar missing knob(thumb).
So this isn't working as scrollbar (TωT)
【Question】
How to make code to this vertical scrollbar working?
This is scrollbar build section in my src file.
Already exists scrollbar, It's almost fine... But maybe missing something.
# Make vertical scrollbar to see all stickers -----------------------
outCV = tk.Canvas(self.iconsFrame, width=GUIController.__windowWidth, height=GUIController.__windowHeight)
scrollbar = tk.Scrollbar(self.iconsFrame, orient=tk.VERTICAL)
scrollbar.config(command=outCV.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
outCV.configure(yscrollcommand=scrollbar.set)
outCV.pack()
# --------------------------------------------------------------------
gridRow = 0
gridCol = 0
for i, tkimg in enumerate(self.tkimgs) :
# Put icons as tile.
Please give me your knowledge.
(FYI) https://github.com/we-yu/L_SL/blob/develop/Canvas_in_Canvas/src/GUICtrl.py Line:196
I found already how to solve it by myself.
There are 2 points in my case.
Should be set scrollregion
Scrollbar works for only Canvas. So, on my case. Need overlapped frames and canvases.
Solving way
self.outerCV = tk.Canvas(self.iconsFrame, width=GUIController.__windowWidth, height=GUIController.__windowHeight)
scrollbar = tk.Scrollbar(self.iconsFrame, orient=tk.VERTICAL)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
scrollbar.config(command=self.outerCV.yview)
self.outerCV.config(scrollregion=(0, 0, 2000, 2000), yscrollcommand=scrollbar.set)
self.outerCV.pack(fill=tk.BOTH)
galleryFrame = tk.Frame(self.outerCV)
self.galleryTag = self.outerCV.create_window((0, 0), window=galleryFrame, anchor=tk.NW, width=self.outerCV.cget('width'))
Put Basement-frame at bottom of window. Then put Canvas on that. Scrollbar attaches on this Canvas.
But if put Canvas on this Canvas, Scrollbar works for scroll, But window won't be working.
So, put frame on scrollbar-canvas. Then Main graphic canvases on that frame.
[Canvas-1][Canvas-2][Canvas-3]...
[Frame for gallery]
[Canvas for Scrollbar]
[Basement frame]

How to fix canvas size not following the configuration

I created a canvas and specified the height using (width, height), but when it comes to the GUI, it doesn't change the size at all.
atEdit=Frame(Attend,borderwidth=0)
atEdit.grid(row=2,column=0,columnspan=5,sticky='news',pady=(0,10),padx=10)
scrollbar = Scrollbar(atEdit)
scrollbar.grid(row=0,column=5,sticky='ns')
canvas = Canvas(atEdit,bg='black',height=20)
canvas.grid(row=0,column=0,columnspan=5,sticky='news')
scrollbar.config(command = canvas.yview)
left = Frame(canvas,borderwidth=0,bg='#dbdbdb',height=2)
left.grid(row=0,column=0,sticky='news')
right=Frame(canvas,borderwidth=0,bg='white',height=2)
right.grid(row=0,column=1,columnspan=4,sticky='news')
left.bind('<Configure>',lambda event,canvas=canvas:self.onFrameConfigure(canvas))
right.bind('<Configure>',lambda event,canvas=canvas:self.onFrameConfigure(canvas))
I add labels to the 'left' and 'right', which increases the size of the frames, but it shouldn't grow larger than the specified size. Is there anyway to make the canvas a fixed size, even if I add more labels to it, and be able to scroll through it using the scrollbar?
def onFrameConfigure(self,canvas):
canvas.configure(scrollregion=canvas.bbox("all"))

Resources