How to make Qtooltip corner round in pyqt5. Tried many stylesheet but it does not work. The frame on Qtooltip remains same.
Qt Style Sheets cannot set the mask of a widget, which is what is required to change the shape of top level widgets to a different one than standard rectangles.
While this question has a partial answer, it's quite old and doesn't explain how to properly use the style hint and implement the style.
The solution is based on a QProxyStyle that updates the returnData of styleHint() by creating a proper mask based on the contents.
The biggest issue here is that there is absolutely no way to know the QSS properties used by a widget (and accessing the widget's styleSheet() is pointless, since style sheets can be inherited): we can never know if a widget has a border, and the possible radius of that border.
There is a way, though: we could locally render the tooltip on a QImage, and use createHeuristicMask() to get a possible mask based on the contents, which should be set using the application stylesheets.
If, for some reason, the mask is still the full rectangle of the widget, we can "guess" a standard rounded mask anyway.
Note that the returnData of styleHint() is a QStyleHintReturn, while we need a QStyleHintReturnMask because we must be able to write its region; in order to achieve this, we use the cast() function of the sip module (usually installed along with PyQt).
The following uses the default palette colors for the tooltip style sheet, but the important aspect is that the border must be slightly different than the background (otherwise the above mask creation won't work).
from PyQt5 import QtCore, QtGui, QtWidgets
import sip
class ProxyStyle(QtWidgets.QProxyStyle):
def styleHint(self, hint, opt=None, widget=None, returnData=None):
if hint == self.SH_ToolTip_Mask and widget:
if super().styleHint(hint, opt, widget, returnData):
# the style already creates a mask
return True
returnData = sip.cast(returnData, QtWidgets.QStyleHintReturnMask)
src = QtGui.QImage(widget.size(), QtGui.QImage.Format_ARGB32)
src.fill(QtCore.Qt.transparent)
widget.render(src)
mask = QtGui.QRegion(QtGui.QBitmap.fromImage(
src.createHeuristicMask()))
if mask == QtGui.QRegion(opt.rect.adjusted(1, 1, -1, -1)):
# if the stylesheet doesn't set a border radius, create one
x, y, w, h = opt.rect.getRect()
mask = QtGui.QRegion(x + 4, y, w - 8, h)
mask += QtGui.QRegion(x, y + 4, w, h - 8)
mask += QtGui.QRegion(x + 2, y + 1, w - 4, h - 2)
mask += QtGui.QRegion(x + 1, y + 2, w - 2, h - 4)
returnData.region = mask
return 1
return super().styleHint(hint, opt, widget, returnData)
app = QtWidgets.QApplication([])
app.setStyle(ProxyStyle())
palette = app.palette()
app.setStyleSheet('''
QToolTip {{
color: {fgd};
background: {bgd};
border: 1px solid {border};
border-radius: 4px;
}}
'''.format(
fgd=palette.color(palette.ToolTipText).name(),
bgd=palette.color(palette.ToolTipBase).name(),
border=palette.color(palette.ToolTipBase).darker(110).name()
))
test = QtWidgets.QPushButton('Hover me', toolTip='Tool tip')
test.show()
app.exec()
Be aware, though, that this is not very optimal, because it involves rendering the tooltip every time it's being shown/resized: if your program shows many tooltips or updates them very frequently, performance can become poor. The alternative is to completely skip the heuristic mask creation and just set a predefined and default mask based on the contents, as shown near the end of the styleHint() override above.
Related
I'm trying to make a Qlabel that will only fill available space in Qt (really in PyQt but the same principle applies). It's meant to display a (sometimes really long) file path, and as I currently have it the length of the label makes the minimum size of that part of the window way too large.
I want to make the label's text be reduced to the maximum possible length without exceeding the minimum width due to the other widgets in the panel. I have found the QFontMetrics::elideText() method, which can effectively clip the text in the way I want, but I still can't figure out how to get the pixel width without the label affecting the size of the panel.
My hackjob thought process is to set the Qlabel's sizes to zero by overriding size/minimumsize/maximumsize, measure the remaining space allotted, and then set text to that. However, I don't know how to get that remaining space, and I feel like there should be a better way.
My layout for reference:
QLabel is a pretty neat widget: it seems very simple, but it's not.
The size and displaying aspects are very important: since it's able to display formatted text, it can even have some layout issues.
Since your requirement is to keep the label as small as possible (but while keeping its content displayed if possible), the most important requirement is to implement the sizeHint (and minimumSizeHint()) functions, since the layout of the parent widget will consider that when resizing its contents.
A possible solution is based on two aspects:
provide a basic [minimum] size hint that doesn't consider the whole contents
override the painting by eliding text whenever the available space is not enough
The following code obviously does NOT consider rich text formatting, including different paragraph alignment, word wrapping, etc.
This is an example showing a subclassed QLabel trying to display the following path:
'/tmp/test_dir/some_long_path/some_subdir/imagepath/'
Consider that you could even actually use a basic QWidget instead. In the following code I'm considering the QFrame subclassing abilities which also include adding proper margins and borders, depending on the style and the frameShape or frameShadow properties.
class ElideLabel(QtWidgets.QLabel):
_elideMode = QtCore.Qt.ElideMiddle
def elideMode(self):
return self._elideMode
def setElideMode(self, mode):
if self._elideMode != mode and mode != QtCore.Qt.ElideNone:
self._elideMode = mode
self.updateGeometry()
def minimumSizeHint(self):
return self.sizeHint()
def sizeHint(self):
hint = self.fontMetrics().boundingRect(self.text()).size()
l, t, r, b = self.getContentsMargins()
margin = self.margin() * 2
return QtCore.QSize(
min(100, hint.width()) + l + r + margin,
min(self.fontMetrics().height(), hint.height()) + t + b + margin
)
def paintEvent(self, event):
qp = QtGui.QPainter(self)
opt = QtWidgets.QStyleOptionFrame()
self.initStyleOption(opt)
self.style().drawControl(
QtWidgets.QStyle.CE_ShapedFrame, opt, qp, self)
l, t, r, b = self.getContentsMargins()
margin = self.margin()
try:
# since Qt >= 5.11
m = self.fontMetrics().horizontalAdvance('x') / 2 - margin
except:
m = self.fontMetrics().width('x') / 2 - margin
r = self.contentsRect().adjusted(
margin + m, margin, -(margin + m), -margin)
qp.drawText(r, self.alignment(),
self.fontMetrics().elidedText(
self.text(), self.elideMode(), r.width()))
You can override the paintEvent() and achieve it in the following way:
class QElidedLabel(QLabel):
def paintEvent(self, event):
painter = QPainter(self)
textDoc = QTextDocument()
metrics = QFontMetrics(self.font())
elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width() - 10)
textDoc.setPlainText(elided)
textDoc.drawContents(painter)
This will draw the Elided Label automatically and you won't have to change your code anywhere else. Also you can set the size policy of QLabel to MinimumExpanding to make sure your QLabel takes the maximum available space. This way the self.width() returns the current maximum width. You can take a look at documentation for the working of QTextDocument() and QFontMetrics(). Also, self.width() - 10 just makes sure that '...' in the elided label is not hidden, you can remove - 10 and just use self.width() if .. visible for you after removing it as well.
In my QT application I'm drawing lots of polygons like this:
I'm animating these, so some polygons will receive a new color. This animation runs 4-5 times per second.
However, calling the paintEvent() of the Qt.Painter() 4-5 times/second redraws ALL polygons which results in performance issues. Its only updated once a second, which is too slow. As you may see in the picture below, only some polygons in the first 12 rows needs to be updated:
[![enter image description here][2]][2]
In the QT docs I have read that you can't really save the state of the things you've already drawn. So you have to redraw everything again. Am I missing something? Is there a trick to still achieve this?
This is what my paintEvent() basically looks like (simplified, reduced cyclomatic complexity)
for y in range(len(self.array)):
for x in range(len(self.array[0])):
if(this): # simplified to reduce cyclomatic complexity
painter.setBrush(QBrush(QColor(20, 0, 255)))
elif(that):
painter.setBrush(QBrush(QColor(175, 175, 175)))
else:
painter.setBrush(QBrush(QColor(0, 0, 0)))
hexa_size = self.array[y][x]
hexas = createHexagon(x, y, hexa_size) # external functions to calculate the hexagon size and position
painter.drawPolygon(hexas)
painter.end()
call (update on each Pin change):
while True:
while(stempel.readPin(0) == 0):
QApplication.processEvents()
time.sleep(0.01)
self.draw_area.update() # Pin state changed, update polygons
while(stempel.readPin(0) == 1):
QApplication.processEvents()
time.sleep(0.01)
Qt allows scheduling an update for only a portion (region) of the widget, thus optimizing the result. This requires two step:
calling update(QRect) with an appropriate rectangle that covers only the part of the widget that requires repainting;
checking the event.rect() and then implement painting in order to paint only that region;
If you know for sure that only the first X rows are going to change color, then:
self.draw_area.update(
QRect(0, 0, self.draw_area.width(), <height of the repainted rows>)
Then, in the paintEvent:
if event.rect().bottom() < <height of the repainted rows>:
rowRange = range(indexOfTheLastRowToRepaint + 1)
else:
rowRange = range(len(self.array))
Note that another solution could be using QPicture, which is a way to "serialize" a QPainter in order to improve performance and avoid unnecessary computations.
class DrawArea(QWidget):
cache = None
def paintEvent(self, event):
if not self.cache:
self.cache = QPicture()
cachePainter = QPainter(self.cache)
# draw on the painter
cachePainter.end()
painter = QPainter(self)
painter.drawPicture(0, 0, self.cache)
def resizeEvent(self, event):
self.cache = None
The code above is very minimalistic, you might create multiple QPictures for every group of row and then decide which one paint whenever you require it, even by combining the event.rect() checking as explained above.
The major benefit of this technique is that QPainter usually processes a QPicture pretty fast, so you don't have to do all computations required for rows, polygons, etc.
Finally, the image you provided seems very repetitive, almost like a texture. In that case, you might consider using a QPixmap for each group of rows and then create a QBrush with that QPixmap. In that case, you'll only need to call painter.fillRect(self.rect(), self.textureBrush).
Solved it myself by using a QGraphicsScene + QGraphicsView:
self.scene = QGraphicsScene()
self.graphicView = QGraphicsView(self.scene, self)
Creating a list where all polygons are being saved:
self.polygons = [ [0] * len(array[0]) for _ in range(len(array))]
Initial drawing of all polygons:
for y in range(len(array)):
for x in range(len(array[0])):
polygon_size = self.array[y][x]
polygon = createPoly(x, y, polygon_size)
self.polygons[y][x] = self.scene.addPolygon(polygon, QPen(Qt.NoPen), QBrush(Qt.black))
if(y % 50 == 0): QApplication.processEvents()
Update rows indivudually:
for poly_size in active_rows:
for active_row in active_rows[poly_size]:
for x in range(0, len(array[0])):
if(array[active_row][x] == int(poly_size)):
self.polygons[active_row][x].setBrush(QBrush(QColor(20, 0, 255)))
if(array[active_row - 2][x] > 0 and array[active_row - 2][x] == int(poly_size)):
self.polygons[active_row - 2][x].setBrush(QBrush(QColor(175, 175, 175)))
I have some trouble with an OpenCV GUI I try to create for a small python application. The GUI is aimed to display an image, to create a set of toggle bars to tune some threshold values which will be use in an image processing algorithm and to display the transformed image next to the original image.
My idea is to manage the GUI through a python class and to store the different threshold values/parameters in the class attributes. I have a method which create the window that I call in the init method and then several other methods (such as one_step() for instance) doing some operations involving the window such as reading the toggle bar values displaying an image, etc ...
'''
def __init__(self, path_to_images):
self._min_canny = 100
self._max_canny = 200
self._blur_kernel_size = 5
self._blur_sigma_value = 0
self._closing_iterations = 1
self._path_to_images = path_to_images
os.chdir(path_to_images)
self._count = 1
self._current_image = cv2.imread(str(self._count) + '.jpg' , 1)
self.initialize_window()
def initialize_window(self):
cv2.namedWindow('Original')
cv2.createTrackbar('Blur Kernel Size', 'Original', self.blur_kernel_size, 10, GUI_seg.on_change)
cv2.createTrackbar('Blur Sigma Value', 'Original', self.blur_sigma_value, 5, GUI_seg.on_change)
cv2.createTrackbar('Min Canny', 'Original', self.min_canny, 255, GUI_seg.on_change)
cv2.createTrackbar('Max Canny', 'Original', self.min_canny, 255, GUI_seg.on_change)
cv2.createTrackbar('Closing Iterations', 'Original', self.closing_iterations, 20, GUI_seg.on_change)
self.one_step()
'''
But when I create an instance of the class and run methods involving the window, no window is opened by the program. I suspect a problem of memory space management (the window is created inside a function and at the end of the execution of the function).
If anyone as an idea of what is going on and how to fix this, I would really appreciate some help with this issue.
I'm trying to create a GUI for a virtual board for the game Go. There should be an nxn grid of tiles where a player can place a stone, either black or white. Clicking on a tile will make it change from tan(the default) to black, click again to white, and click a third time to go back to tan. Player one can click once on a spot to place a stone there, and player two can click twice (you need to remove stones later, so three clicks resets it). I created a tile object and then used a nested for loop to instantiate 9 by 9 of them. Unfortunately, running the code only seems to produce 1 functional tile, not 81. This code should work on any python machine (I'm using Python 3.4), so you can try to run it and see for yourself. Can anyone point out the reason the loop is only running once?
from tkinter import *
window = Tk()
n = 9
"""
A tile is a point on a game board where black or white pieces can be placed. If there are no pieces, it remains tan.
The basic feature is the "core" field which is a tkinter button. when the color is changed, the button is configured to represent this.
"""
class tile(object):
core = Button(window, height = 2, width = 3, bg = "#F4C364")
def __init__(self):
pass
"""the cycle function makes the tile object actually change color, going between three options: black, white, or tan."""
def cycle(self):
color = self.core.cget("bg")
if(color == "#F4C364"): #tan, the inital value.
self.core.config(bg = "#111111")#white.
elif (color == "#111111"):
self.core.config(bg = "#DDDDDD")#black.
else:
self.core.config(bg = "#F4C364")#back to tan.
board = [] #create overall array
for x in range(n):
board.append([])#add subarrays inside it
for y in range(n):
board[x].append(tile())#add a tile n times in each of the n subarrays
T = board[x][y] #for clarity, T means tile
T.core.config(command = lambda: T.cycle()) #I do this now because cycle hadn't been defined yet when I created the "core" field
T.core.grid(row = x, column = y) #put them into tkinter.
window.mainloop()
As mhawke points out in his answer you need to make the core an instance variable, so that each Tile gets its own core.
And as I mention in my comment above, you also need to fix the Button's command callback function. The code you use in your question will call the .cycle() method of the current value of T, which happens to be the last tile created. So no matter where you click only the last tile changes color. One way to fix that is to pass the current tile as a default argument of the lambda function when you create it. But because you are using OOP to create your Tile there's a better way, which you can see below.
I've made a few modifications to your code.
Although many Tkinter examples use from tkinter import * it's not a good practice. When you do from some_module import * it brings all of the names from some_module into the current module (your script), which means you could accidentally override those names with your own names. Even worse, if you do import * with multiple modules each new module's names can clash with the previous module's names, and you have no way of knowing that's happened until you start getting mysterious bugs. Using import tkinter as tk means you need to do a little more typing, but it makes the resulting program less bug-prone and easier to read.
I've modified the __init__ method so that it is called with the window and the (x, y) location of the tile (it's customary to use x for the horizontal coordinate and y for the vertical coordinate). Each Tile object now keeps track of its current state, where 0=empty, 1=black, 2=white. This makes it easier to update the colors. And because we've passed in the window and (x, y) we can use that info to add the tile to the grid. The tile also remembers the location (in self.location), which may come in handy.
I've modified the cycle method so that it updates both the background color and the activebackground of the tile. So when the mouse hovers over the tile it changes to a color that's (roughly) halfway between its current color and the color it will turn if you click it. IMO, this is nicer than the tile always turning pale grey when the mouse hovers over it.
I've also optimized the code that creates all the tiles and stores them in the board list of lists.
import tkinter as tk
colors = (
#background, #activebackground
("#F4C364", "#826232"), #tan
("#111111", "#777777"), #black
("#DDDDDD", "#E8C8A8"), #white
)
class Tile(object):
""" A tile is a point on a game board where black or white pieces can be placed.
If there are no pieces, it remains tan.
The basic feature is the "core" field which is a tkinter button.
when the color is changed, the button is configured to represent this.
"""
def __init__(self, win, x, y):
#States: 0=empty, 1=black, 2=white
self.state = 0
bg, abg = colors[self.state]
self.core = tk.Button(win, height=2, width=3,
bg=bg, activebackground=abg,
command=self.cycle)
self.core.grid(row=y, column=x)
#self.location = x, y
def cycle(self):
""" the cycle function makes the tile object actually change color,
going between three options: black, white, or tan.
"""
#cycle to the next state. 0 -> 1 -> 2 -> 0
self.state = (self.state + 1) % 3
bg, abg = colors[self.state]
self.core.config(bg=bg, activebackground=abg)
#print(self.location)
window = tk.Tk()
n = 9
board = []
for y in range(n):
row = [Tile(window, x, y) for x in range(n)]
board.append(row)
window.mainloop()
The problem is that core is a class variable which is created once and shared by all instances of class tile. It should be an instance variable for each tile instance.
Move core = Button(window, height = 2, width = 3, bg = "#F4C364") into tile.__init__() like this:
class Tile(object):
def __init__(self):
self.core = Button(window, height = 2, width = 3, bg = "#F4C364")
The root of the problem is that core is shared by all instances of the class by virtue of how you've defined it. You need to move creation of the button into the initializer.
I also suggest moving the configuration of the command into the button itself. The caller shouldn't need (nor care) how the button works internally. Personally I'd have the tile inherit from Button, but if you favor composition over inheritance I'll stick with that.
Example:
class tile(object):
def __init__(self):
self.core = Button(window, height = 2, width = 3, bg = "#F4C364"
command=self.cycle)
I need to Solve two problems With my widget above.
I'd like to be able to define the amount of space put between the post widgets shown in the image (they look fine as is, but I wanna know it's done).
I'd like to grow the text edits vertically based on the amount of text they contain without growing horizontally.
For 1 the code that populates the widgets is as follows :
self._body_frame = QWidget()
self._body_frame.setMinimumWidth(750)
self._body_layout = QVBoxLayout()
self._body_layout.setSpacing(0)
self._post_widgets = []
for i in range(self._posts_per_page):
pw = PostWidget()
self._post_widgets.append(pw)
self._body_layout.addWidget(pw)
self._body_frame.setLayout(self._body_layout)
SetSpacing(0) doesn't bring things any closer, however SetSpacing(100) does increase it.
edit
(for Question 2) I haven't mentioned this, but I want the parent widget to have a vertical scrollbar.
I have answered my own question, but its wordy, and cause and affect based. A proper well written tutorial style answer to address both points gets the bounty :D
edit 2
Using my own answer below I have solved the problem. I'll be accepting my own answer now.
1) Layouts
The other answer on here is very unclear and possibly off about how layout margins work. Its actually very straightforward.
Layouts have content margins
Widgets have content margins
Both of these define a padding around what they contain. A margin setting of 2 on a layout means 2 pixels of padding on all sides. If you have parent-child widgets and layouts, which is always the case when you compose your UI, each object can specific margins which take effect individually. That is... a parent layout specifying a margin of 2, with a child layout specifying a margin of 2, will effectively have 4 pixels of margin being displayed (obviously with some frame drawing in between if the widget has a frame.
A simple layout example illustrates this:
w = QtGui.QWidget()
w.resize(600,400)
layout = QtGui.QVBoxLayout(w)
layout.setMargin(10)
frame = QtGui.QFrame()
frame.setFrameShape(frame.Box)
layout.addWidget(frame)
layout2 = QtGui.QVBoxLayout(frame)
layout2.setMargin(20)
frame2 = QtGui.QFrame()
frame2.setFrameShape(frame2.Box)
layout2.addWidget(frame2)
You can see that the top level margin is 10 on each side, and the child layout is 20 on each side. Nothing really complicated in terms of math.
Margin can also be specified on a per-side basis:
# left: 20, top: 0, right: 20, bottom: 0
layout.setContentsMargins(20,0,20,0)
There is also the option of setting spacing on a layout. Spacing is the pixel amount that is placed between each child of the layout. Setting it to 0 means they are right up against each other. Spacing is a feature of the layout, while margin is a feature of the entire object. A layout can have margin around it, and also spacing between its children. And, the children of the widget can have their own margins which are part of their individual displays.
layout.setSpacing(10) # 10 pixels between each layout item
2) Auto-Resizing QTextEdit
Now for the second part of your question. There are a few ways to create a auto-resizing QTextEdit I am sure. But one way to approach it is to watch for content changes in the document, and then adjust the widget based on the document height:
class Window(QtGui.QDialog):
def __init__(self):
super(Window, self).__init__()
self.resize(600,400)
self.mainLayout = QtGui.QVBoxLayout(self)
self.mainLayout.setMargin(10)
self.scroll = QtGui.QScrollArea()
self.scroll.setWidgetResizable(True)
self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.mainLayout.addWidget(self.scroll)
scrollContents = QtGui.QWidget()
self.scroll.setWidget(scrollContents)
self.textLayout = QtGui.QVBoxLayout(scrollContents)
self.textLayout.setMargin(10)
for _ in xrange(5):
text = GrowingTextEdit()
text.setMinimumHeight(50)
self.textLayout.addWidget(text)
class GrowingTextEdit(QtGui.QTextEdit):
def __init__(self, *args, **kwargs):
super(GrowingTextEdit, self).__init__(*args, **kwargs)
self.document().contentsChanged.connect(self.sizeChange)
self.heightMin = 0
self.heightMax = 65000
def sizeChange(self):
docHeight = self.document().size().height()
if self.heightMin <= docHeight <= self.heightMax:
self.setMinimumHeight(docHeight)
I subclassed QTextEdit -> GrowingTextEdit, and connected the signal emitted from its document to a slot sizeChange that checks the document height. I also included a heightMin and heightMax attribute to let you specify how large or small its allowed to autogrow. If you try this out, you will see that as you type into the box, the widget will start to resize itself, and also shrink back when you remove lines. You can also turn off the scrollbars if you want. Right now each text edit has its own bars, in addition to the parent scroll area. Also, I think you could add a small pad value to the docHeight so that it expands just enough to not show scrollbars for the content.
This approach is not really low level. It uses the commonly exposed signals and child members of the widget for you to receive notifications of state changes. Its pretty common to make use of the signals for extending functionality of existing widgets.
To Address Question 1:
Parent Widgets and Layouts both have margins, in addition to the spacing parameter of the layout itself. From some cause and affect testing It is apprent that margins apply both to the outer region of a parent as well as an internal region.
So, for example if a 2 pixel margin is specified to a parent the vertical border has <--2 pixel | 2 pixel --> margin in addition to the margins of the layout (A HBoxLayout in this case). If the layout has a 2 pixel margin as well the area around horizontal line would look like:
<-- 2 pixel | 2 pixel --> <-- 2 pixel (L) 2 pixel--> (W)
edit Perhaps its more like this: | 2 pixel --> (L) 2 pixel --> <-- 2 pixel (W)
Where | is the vertical line of the parent (L) is the vertical line of the Layout and (W) is the border of the embedded widget in the horizontal layout.
The spacing of the layout is an additional parameter that controls how much space is inserted between widgets of the layout in addition to any layout margins.
The description above might not be accurate( so feel free to edit it where it is inaccurate), but setting the margins of the parent and the layout to zero as well as the layouts spacing to zero produces the result you are after.
For point 2:
I do not think there is a straight forward way to address this issue (you probably have to resort to hooking in at a lower level, which requires a deeper understanding of the API). I think you should use the QLabel Widget instead of the QTextEdit widget. Labels do not have a view and thus expand as needed, at least thats how they work by default, as long as the parent isn't constrained in it's movement.
So, change the QTextEdit to Qlabel and add a scrolling view to the parent and everything should work as you want. I have a feeling you chose QTextEdit because of it's background. Research the way HTML works in QT widgets and you might be able to alter the background via HTML.
edit
This widget (excuse the size) is created by the following code on OS X with PyQT:
import sys
from PyQt4 import Qt
class PostMeta(Qt.QWidget):
posted_at_base_text = "<b> Posted At:</b>"
posted_by_base_text = "<b> Posted By:</b>"
def __init__(self):
Qt.QWidget.__init__(self)
self._posted_by_label = Qt.QLabel()
self._posted_at_label = Qt.QLabel()
layout = Qt.QVBoxLayout()
layout.setMargin(0)
layout.setSpacing(5)
layout.addWidget(self._posted_by_label)
layout.addWidget(self._posted_at_label)
layout.addStretch()
self.setLayout(layout)
self._posted_by_label.setText(PostMeta.posted_by_base_text)
self._posted_at_label.setText(PostMeta.posted_at_base_text)
class FramePost(Qt.QFrame):
def __init__(self):
Qt.QFrame.__init__(self)
layout = Qt.QHBoxLayout()
layout.setMargin(10)
self.te = Qt.QLabel()
self.te.setStyleSheet("QLabel { background : rgb(245,245,245) }")
self.te.setFrameStyle( Qt.QFrame.Panel | Qt.QFrame.Sunken)
self.te.setLineWidth(1)
self._post_meta = PostMeta()
layout.addWidget(self._post_meta)
vline = Qt.QFrame()
vline.setFrameShape(Qt.QFrame.VLine)
layout.addWidget(vline)
layout.addWidget(self.te)
self.te.setText(
""" line one
line two
line three
line four
line five
line six
line seven
line eight
line nine
line ten
line eleven
line twelve
line thirteen""")
self.setLayout(layout)
self.setFrameStyle(Qt.QFrame.Box)
self.setLineWidth(2)
app = Qt.QApplication(sys.argv)
w = Qt.QWidget()
layout = Qt.QHBoxLayout()
fp = FramePost()
layout.addWidget(fp)
w.setLayout(layout)
w.show()
app.exec_()
The labels in the left widget show the spacer and margin tweaking done, and I've used a QLabel for the post text. Notice I've tweaked the label to look a bit more like a default QTextEdit