How to set graphics for a push button using Qt Designer? - python-3.x

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.

Related

method to draw circular buttons in pyqt5

i would like to try to create a row of circular buttons with a method different with the one that uses setStyleSheets to define the color, the thickness and so on. The code that I managed to get with that method is the following:
self.button_list = []
for i in range(10):
button = QPushButton("{}".format(i+1), self)
button.setFixedSize(QSize(50, 50))
button.setStyleSheet("border : 1px solid black;background-color : green; color: yellow; border-radius : 25px")
self.button_list.append(button)
mainlayout.addWidget(button, i)
With what other method can I achieve the same results of circular buttons?

fabric.js: How to set stroke thickness of selection box and controls?

In fabric.js, how do you adjust stroke thickness for the object selection box and control handles?
There's a bunch of customization options available, however it isn't clear on how you can customized stroke thickness. Is it possible, perhaps through an indirect way?
Note: The properties selectionColor, selectionBorderColor, selectionLineWidth are misleading... they have to do with the temporary selection box that appears when attempting to do a fresh drag-select across the canvas. Once your selection is made, it disappears and then you see the persistent object selection box with control handles (the thing I'm trying to customize).
See links:
http://fabricjs.com/customization
http://fabricjs.com/controls-customization
Ok here's a 2-part solution:
https://codepen.io/MarsAndBack/pen/bGExXzd
For the selection box stroke thickness:
Use fabric.Object.prototype.set to customize any object selection globally. Also, borderScaleFactor is documented, but not included in the fabric.js customization demos:
fabric.Object.prototype.set({
borderScaleFactor: 6
})
For the control handle stroke thickness:
Here we override differently, and actually draw new elements using standard HTML5 canvas properties. Through this method you could also target specific control handles and even use image icons.
fabric.Object.prototype._drawControl = controlHandles
fabric.Object.prototype.cornerSize = 20
function controlHandles (control, ctx, methodName, left, top) {
if (!this.isControlVisible(control)) {
return
}
var size = this.cornerSize
// Note 1: These are standard HTML5 canvas properties, not fabric.js.
// Note 2: Order matters, for instance putting stroke() before strokeStyle may result in undesired effects.
ctx.beginPath()
ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false);
ctx.fillStyle = "pink"
ctx.fill()
ctx.lineWidth = 4 // This is the stroke thickness
ctx.strokeStyle = "red"
ctx.stroke()
}
SO code snippet:
const canvas = new fabric.Canvas("myCanvas")
canvas.backgroundColor="#222222"
this.box = new fabric.Rect ({
width: 240,
height: 100,
fill: '#fff28a',
myType: "box"
})
canvas.add(this.box)
this.box.center()
// Selection box border properties
// ----------------------------------------
fabric.Object.prototype.set({
borderColor: "white",
borderScaleFactor: 6
})
// Control handle properties
// ----------------------------------------
fabric.Object.prototype._drawControl = controlHandles
fabric.Object.prototype.cornerSize = 20
function controlHandles (control, ctx, methodName, left, top) {
if (!this.isControlVisible(control)) {
return
}
var size = this.cornerSize
// Note 1: These are standard HTML5 canvas properties, not fabric.js.
// Note 2: Order matters, for instance putting stroke() before strokeStyle may result in undesired effects.
ctx.beginPath()
ctx.arc(left + size/2, top + size/2, size/2, 0, 2 * Math.PI, false);
ctx.fillStyle = "pink"
ctx.fill()
ctx.lineWidth = 4
ctx.strokeStyle = "red"
ctx.stroke()
}
<script src="https://pagecdn.io/lib/fabric/3.6.3/fabric.min.js"></script>
<canvas id="myCanvas" width="700" height="400"></canvas>

QML Row vs. RowLayout

I'm trying to write a topbar for my application that should contain mainly the app logo (a small image) and the app title (just text). Moreover, I'd like this topbar to automatically resize according to the window's height.
I'm new to QML, but I suppose that I should wrap these components inside a Row or a RowLayout component. This is my sample code:
import QtQuick 2.0
import QtQuick.Layouts 1.0
Rectangle
{
id: mainwindow
width: 1024
height: 600
Row
{
id: rowlayout
height: logoimage.height
spacing: 5
property int count: 3
anchors
{
left: parent.left
right: parent.right
top: parent.top
}
Image
{
id: logoimage
source: "qrc:/images/resources/images/icon.png"
height: mainwindow.height / 20
anchors.top: parent.top
anchors.left: parent.left
}
Text
{
id: logotext
text: qsTr("This is my logo text")
font.pixelSize: parent.height
font.family: "Sans Serif"
height: parent.height
verticalAlignment: Text.AlignVCenter
anchors.top: parent.top
anchors.left: logoimage.right
}
/*
Rectangle
{
id: otherrect
height: parent.height
color: "lightgreen"
anchors.top: parent.top
anchors.left: logotext.right
anchors.right: parent.right
}
*/
}
}
I tell to the Row component that its height should follow the logo's height, and to the Image (logo) component that its height should be 1/20th of the Rectangle (mainwindow) component.
Using a Row container, the code behaves as expected but I get an annoying warning (QML Row: Cannot specify left, right, horizontalCenter, fill or centerIn anchors for items inside Row. Row will not function.) and I have to do a lot of anchoring. Conversely, if I use a RowLayout container, I can remove most of the anchors but the Image completely ignores its height attribute (but the text still resizes correctly). So the questions are:
is this a bug of the RowLayout component? I'm using Qt-5.1.0-Beta with Android support, so this could be an explanation
how can I use a Row component without using anchors in its children and thus avoid the warning?
I'm missing something important or I'm almost on the right track but I have to bear with this beta of Qt until a stable version is released?
You said that you get the expected behavior with Row, so you should probably use it. The warning that Row is giving is asking you to remove the vertical anchors (top and bottom) from its child elements.
The Row element provides horizontal (left and right) anchor-like behavior for its child elements, but it doesn't mind if you use top and bottom anchors (notice that top and bottom were not in the warning).
In other words remove "anchors.left" and/or "anchors.right" lines from "logoimage", "logotext", and "otherrect" (if you plan on uncommenting it at some point), but not the "anchors.top" lines, and that should stop the warning and keep the correct behavior.
An alternative is to just remove the Row element and use Item or FocusScope (if you plan on having input elements in your "top bar" area), which will not try to take over anchoring operations, and that may be a better fit for you if you really like anchors.
You need to give a width to your layout if you want it to strecht its children, either with width: parent.width, or better, with anchors { left: parent.left; right: parent.right } and no anchors on vertical lines inside childrens of the layout.
1) NO, it is no a bug of RowLayout
2) Consider that RowLayout is preferred to Row because is most expressive for components placing. The Row component is better that Rowlayout only for graphics or animation apps
3) The stable version is now available, but your errors are not bugs ;)

Setting Selected property of Row in TreeView dynamically in Gtk3 (python)

This is the same problem as in my previous question, but I moved to python3/gtk3 to be able to use a css for setting the basic properties.
From the python file:
self.w = Gtk.Window()
self.w.set_name("App")
I can use a css:
#App GtkTreeView row:selected {
border-color: #000000;
border-top-width: 1px;
border-bottom-width: 1px;
color: #000;
}
And easily permanently change the style of the selection. To me this means that I should be able to dynamically get access to the row-object and its style where I could set the bg for the Gtk.StateFlags.SELECTED.
I've tried a bunch of weird ways, e.g (where bg_color is a Gdk.Color that works fine for e.g. changing the style of a Label outside the TreeView).
style=self.treeview.get_style_context()
col = style.get_background_color(Gtk.StateFlags.SELECTED)
col.alpha = 1.0
col.blue = bg_color.blue
col.red = bg_color.red
col.green = bg_color.green
Or:
style = self.treeview.get_style().copy()
style.bg[Gtk.StateFlags.SELECTED] = bg_color
self.treeview.set_style(style)
(produces error: style.bg[Gtk.StateFlags.SELECTED] = bg_color
IndexError: list assignment index out of range)
etcetera...
So please, how do I find the way to dynamically change the selection effect depending on the normal-color of the row? Or in other words, how do I find my way to the object that actually holds the style-setting for the selection?
I had one last idea about how it could be done after posting which actually ended up working:
Reloading the css dynamically:
In the css I added a row leaving the value for the background open to dynamic substitution:
#App GtkTreeView row:selected {
border-color: #400;
border-top-width: 2px;
border-bottom-width: 2px;
background: {0};
color: #000;
}
Then I loaded the css in python:
screen = Gdk.Screen.get_default()
self._css_provider = Gtk.CssProvider()
css = open("notify_stack.css", 'rb')
self._css = css.read()
css.close()
self._css_from = bytes("{0}".encode("utf8"))
self._css_provider.load_from_data(self._css.replace(
self._css_from,
bytes("#fff".encode("utf8"))))
context = Gtk.StyleContext()
context.add_provider_for_screen(screen, self._css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
Later in the callback for when a row is selected I put this row (data is my ListStore):
self._css_provider.load_from_data(
self._css.replace(self._css_from,
bytes(data[rows[0]][self.BG_COLOR].encode("utf8"))))
It feels really brute, there must be a nicer way, but hey it actually worked.

GridLayout+ScrollArea widget position after size change

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.

Resources