Put a bitmap on top of StaticBitmap in WxPython - python

I have an application that has a panel
on which static bitmaps (wx.StaticBitmap) are placed
in a GridBagSizer.
I want to place a bitmap on top of certain of
the static bitmaps, that will partly cover it and its neighbours.
I've been trying like this:
self.panel = wx.Panel(self, -1, style=wx.TRANSPARENT_WINDOW)
self.panel.Bind(wx.EVT_PAINT, self.place_bitmap)
.
.
.
def place_bitmap(self, *args):
dc = wx.ClientDC(self.panel) # or wx.PaintDC(self.panel)
for xy in self.coordinates:
dc.DrawBitmap(self.top_bitmap, xy[0], xy[1], 0)
self.panel.Layout()
If I change the coordinates in dc.DrawBitmap()
to a place in self.panel where there is no static bitmap, it shows.
But I can't get it to show on top of the static bitmaps.
So it seems it's getting rendered under them?
How can I make the top_bitmap go on top?

You can't draw on top of the child windows portably. The general solution is to create a child window of that child window and draw on it but in your case it probably would be simpler to just use a custom window instead of wxStaticBitmap which is a trivially simple class anyhow -- then you'd be able to draw whatever you want in it.

Related

pyqt5 draggable rectangles on picture

I am very new to pyqt5 and trying to figure out how to replicate the following idea.
Load an image.
Draw MOVEABLE rectangle(s) on top.
-- by Moveable, I mean rectangles that I can move via click and drag after initial placement. Like if I dropped it on the top right of picture, I can later move it to middle of the picture.
-- rectangles(s) multiple is very important. hopefully I can add/remove them dynamically too.
ideally show a text associated with the rectangle, like a, b, c... that is editable. (nice to have)
The following picture from LabelImg show cases the idea very well.
At this point in time, I have been able to load a picture and display it via QLabels and converting to PixMap. The self.ui, is the typical syntax for loading a UI from QT Designer.
class ImageLoader(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.ui = Ui_Form() # from QT Designer
self.ui.setupUi(self)
self.ui.frame_image_label.setMouseTracking(True)
self.ui.frame_image_label.setAcceptDrops(True)
#.... other stuff not quite relevant #####
# a special draggable Label (see below)
# The following does NOT work. (And I want to be able to make multiple labels on key-press...)
self.special_label = DragLabel('hii', self.ui.frame_image_label)
def go_to_image(self):
'''Load the image from a video into the base QLabel to show'''
self.current_frame_number, img = self.video_folder.get_frame(self.current_frame_number)
if img is None:
return
img = self.video_folder.resize_image(image = img)
pixmap = convertCvImage2QtImage(img)
if pixmap.isNull():
return
self.ui.frame_image_label.setPixmap(pixmap)
I have also created a custom DragLabel
class DragLabel(QLabel):
def __init__(self, button_text, parent):
super().__init__(button_text, parent)
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
mimeData = QMimeData()
drag = QDrag(self)
drag.setMimeData(mimeData)
pixmap = QPixmap(self.size())
self.render(pixmap)
drag.setPixmap(pixmap)
drag.exec_(Qt.MoveAction)
The current issues:
The special_label does show up in my UI. But it doesn't move when I drag it.
It is not constrained to my self.ui.frame_image_label area
In general, the reason Im not using the LabelImg, which is very nice, is because I'm trying to label a VIDEO, and am mainly use this as "correction", ie finding where the Area of Interest was incorrectly identified and then hand-correcting them. Hence the need to able to move my selections. Also, I am trying to embed "play/pause", slider selection of frames for ease of use.
Any help is appreciated.
Edit:
Musicamante has the correct idea in that we should swap to the Graphics View Framework and use views and scenes.
QLabels with pictures is not the correct idea.

How to add an invisible line in PyQt5

This attached image is the screenshot of an application developed using PyQt5.
The image clearly has an invisible line running in the middle of the boxes enclosing the contents.
What code should I add in my program to draw an invisible line overlaying all other objects created earlier. I couldn't find any documentation regarding this but as the image suggests, it has somehow been implemented.
A code snippet is not needed to be provided by me since this is a question about adding/developing a feature rather than debugging or changing any existing code.
Premise: what you provided as an example doesn't seem a very good thing to do. It also seems more a glich than a "feature", and adding "invisible" lines like that might result in an annoying GUI for the user. The only scenario in which I'd use it would be a purely graphical/fancy one, for which you actually want to create a "glitch" for some reason. Also, note that the following solutions are not easy, and their usage requires you an advanced skill level and experience with Qt, because if you don't really understand what's happening, you'll most certainly encounter bugs or unexpected results that will be very difficult to fix.
Now. You can't actually "paint an invisible line", but there are certain work arounds that can get you a similar result, depending on the situation.
The main problem is that painting (at least on Qt) happens from the "bottom" of each widget, and each child widget is painted over the previous painting process, in reverse stacking order: if you have widgets that overlap, the topmost one will paint over the other. This is more clear if you have a container widget (such as a QFrame or a QGroupBox) with a background color and its children use another one: the background of the children will be painted over the parent's.
The (theoretically) most simple solution is to have a child widget that is not added to the main widget layout manager.
Two important notes:
The following will only work if applied to the topmost widget on which the "invisible line" must be applied.
If the widget on which you apply this is not the top level window, the line will probably not be really invisible.
class TestWithChildLine(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
for row in range(3):
for col in range(6):
layout.addWidget(QtWidgets.QDial(), row, col)
# create a widget child of this one, but *do not add* it to the layout
self.invisibleWidget = QtWidgets.QWidget(self)
# ensure that the widget background is painted
self.invisibleWidget.setAutoFillBackground(True)
# and that it doesn't receive mouse events
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
def resizeEvent(self, event):
super().resizeEvent(event)
# create a rectangle that will be used for the "invisible" line, wide
# as the main widget but with 10 pixel height, then center it
rect = QtCore.QRect(0, 0, self.width(), 10)
rect.moveCenter(self.rect().center())
# set the geometry of the "invisible" widget to that rectangle
self.invisibleWidget.setGeometry(rect)
Unfortunately, this approach has a big issue: if the background color has an alpha component or uses a pixmap (like many styles do, and you have NO control nor access to it), the result will not be an invisible line.
Here is a screenshot taken using the "Oxygen" style (I set a 20 pixel spacing for the layout); as you can see, the Oxygen style draws a custom gradient for window backgrounds, which will result in a "not invisible line":
The only easy workaround for that is to set the background using stylesheets (changing the palette is not enough, as the style will still use its own way of painting using a gradient derived from the QPalette.Window role):
self.invisibleWidget = QtWidgets.QWidget(self)
self.invisibleWidget.setObjectName('InvisibleLine')
self.invisibleWidget.setAutoFillBackground(True)
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
self.setStyleSheet('''
TestWithChildFull, #InvisibleLine {
background: lightGray;
}
''')
The selectors are required to avoid stylesheet propagation to child widgets; I used the '#' selector to identify the object name of the "invisible" widget.
As you can see, now we've lost the gradient, but the result works as expected:
Now. There's another, more complicated solution, but that should work with any situation, assuming that you're still using it on a top level window.
This approach still uses the child widget technique, but uses QWidget.render() to paint the current background of the top level window on a QPixmap, and then set that pixmap to the child widget (which now is a QLabel).
The trick is to use the DrawWindowBackground render flag, which allows us to paint the widget without any children. Note that in this case I used a black background, which shows a "lighter" gradient on the borders that better demonstrate the effect:
class TestWithChildLabel(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
layout.setSpacing(40)
for row in range(3):
for col in range(6):
layout.addWidget(QtWidgets.QDial(), row, col)
self.invisibleWidget = QtWidgets.QLabel(self)
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
palette = self.palette()
palette.setColor(palette.Window, QtGui.QColor('black'))
self.setPalette(palette)
def resizeEvent(self, event):
super().resizeEvent(event)
pm = QtGui.QPixmap(self.size())
pm.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(pm)
maskRect = QtCore.QRect(0, 0, self.width(), 50)
maskRect.moveTop(50)
region = QtGui.QRegion(maskRect)
self.render(qp, maskRect.topLeft(), flags=self.DrawWindowBackground,
sourceRegion=region)
qp.end()
self.invisibleWidget.setPixmap(pm)
self.invisibleWidget.setGeometry(self.rect())
And here is the result:
Finally, an further alternative would be to manually apply a mask to each child widget, according to their position. But that could become really difficult (and possibly hard to manage/debug) if you have complex layouts or a high child count, since you'd need to set (or unset) the mask for all direct children each time a resize event occurs. I won't demonstrate this scenario, as I believe it's too complex and unnecessary.

WxPython - How to hide the X and expand button on window

Im making a python program and in some functions it needs to hide the X and expand window buttons, how would i do it? Im using WxPython, how would I put this in?
The widgets in the window frame are defined as part of the window's style: CLOSE_BOX, MINIMIZE_BOX, and MAXIMIZE_BOX.
So, when you create the window, just leave those styles out.
If you're using a wx.Frame subclass, note that DEFAULT_FRAME_STYLE includes these values, so you will have to mask them out:
style = wx.DEFAULT_FRAME_STYLE & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX)
super().__init__(whatever, args, you, use, style=style)
If you want to change them after creation, you use SetWindowStyle:
style = self.GetWindowStyle()
self.SetWindowStyle(style & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX))
self.Refresh()
However, notice that the documentation of that function says:
Please note that some styles cannot be changed after the window creation and that Refresh() might need to be called after changing the others for the change to take place immediately.
And, from what I can tell, on Windows, if you create a window with a close box and then remove it later in this way, it doesn't actually go away. It does disable, which may be good enough. But if not, there's probably no way to do what you want without either reaching underneath wx to the native Windows API (which gets very tricky), or drawing the widgets on the frame manually (which gets even more tricky, especially if you care about looking right on different versions of Windows—not to mention porting to other platforms).
I wrote about Frame styles a while ago on my blog. To remove all the buttons, you could do this:
import wx
########################################################################
class NoSystemMenuFrame(wx.Frame):
"""
There is no system menu, which means the title bar is there, but
no buttons and no menu when clicking the top left hand corner
of the frame
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
no_sys_menu = wx.CAPTION
wx.Frame.__init__(self, None, title="No System Menu", style=no_sys_menu)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = NoSystemMenuFrame()
app.MainLoop()
I tried setting the style to wx.DEFAULT_FRAME_STYLE & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX) and to wx.DEFAULT_FRAME_STYLE^(wx.CLOSE_BOX|wx.MAXIMIZE_BOX), but both of those seem to only remove the Close box. For some reason, the Maximize button is still there on my Xubuntu machine.

Better way to Set Background Color with wx.DC

Right now, I am setting the background colour like this,
dc.DrawRectangle(0,0,width,height)
Do you know a better way to set the background color?
http://wxpython.org/docs/api/wx.DC-class.html
If you're already painting on a wx.DC to draw the rest of the window's content then the best way is to set the background brush, and then clear the DC. Something like this:
def OnPaint(self, event):
dc= wx.PaintDC(self)
dc.SetBackground(wx.Brush(someColour))
dc.Clear()
# other drawing stuff...
If you are setting the colour with the window's SetBackgroundColour method, then you can use that instead of some fixed colour value, like this:
def OnPaint(self, event):
dc= wx.PaintDC(self)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
# other drawing stuff...
The normal way, which works for most windows, including buttons and other widgets is to call wxWindow::SetBackgroundColour()
http://docs.wxwidgets.org/2.8/wx_wxwindow.html#wxwindowsetbackgroundcolour
The neat thing is that if you call this on the topmost parent, all the children will automatically inherit the same background colour. See the link for how exactly this works.

Delete image in wxpython?

I know this sounds easy but how can i delete a image with wxpython?
I am using wx.StaticBitmap and wx.Image to create the image, but is there a way to delete an image?
Maybe theres something like this:
bmp1 = wx.Image(filename, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.bitmap1 = wx.StaticBitmap(self.sizer, -1, bmp1, (0, 0))
delete_image(self.bitmap1)
delete_image(bmp1)
I have tried replacing the variable but the image still shows.
self.bitmap1.Hide() or self.bitmap1.Destroy(). The first will just hide it whereas the second will actually destroy the widget. You'll probably want to call self.Layout() to make your window redraw and update the layout of your widgets.

Categories