wx python image refresh on Windows - linux

I have an application that involves displaying multiple images. This works as I would expect on linux, but on Windows there is an annoying flash as the images are painted. This is best seen as a little square in the top left-hand corner of the screen where a flash of colour appears. Am I not approaching this requirement in the right way? Or is there some fix I should be applying to overcome the Windows effect? Or is it just my version on Windows (I only have one to test it: Windows 7 Ultimate)?
I have tried Freeze and Thaw in refresh_sizer_cell but it didn't behave as I expected
import wx
class ImageSizer(wx.Frame):
BACKGROUND_COLOUR = (246, 244, 242)
def __init__(self, parent, title):
super(ImageSizer, self).__init__(parent, title=title)
self.main_sizer = wx.GridBagSizer()
self.SetSizer(self.main_sizer)
cmd_reset = wx.Button(self, label='Reset')
cmd_reset.Bind(wx.EVT_BUTTON, self.on_cmd_reset_click)
cmd_cancel = wx.Button(self, label='Cancel')
cmd_cancel.Bind(wx.EVT_BUTTON, self.on_cmd_cancel_click)
self.main_sizer.Add((400, 0), pos=(0, 0), span=(1, 2)) # dummy to position Available
self.main_sizer.Add((0, 100), pos=(1, 0), span=(1, 1)) # dummy to position Buttons
self.main_sizer.Add(cmd_reset, pos=(2, 2), flag=wx.LEFT | wx.TOP, border=10)
self.main_sizer.Add(cmd_cancel, pos=(2, 3), flag=wx.RIGHT | wx.BOTTOM | wx.TOP | wx.ALIGN_RIGHT, border=10)
self.SetBackgroundColour(self.BACKGROUND_COLOUR)
self.shape_types = {'available': 0, 'selected': 1}
self.available_shapes = []
self.selected_shapes = []
self.initialise()
self.Center()
self.Fit()
self.Show()
def initialise(self):
self.available_shapes = ['square', 'circle', 'triangle', 'cross']
self.selected_shapes = []
self.display_images()
def display_images(self):
available_sizer = ShapeSizer(self, self.available_shapes, self.shape_types['available'])
self.refresh_sizer_cell(self.main_sizer, available_sizer, (1, 2), (1, 3))
selected_sizer = ShapeSizer(self, self.selected_shapes, self.shape_types['selected'])
self.refresh_sizer_cell(self.main_sizer, selected_sizer, (1, 1), (2, 1))
self.Layout()
#staticmethod
def refresh_sizer_cell(sizer, item, pos, span, flag=wx.ALL, border=10):
old_item = sizer.FindItemAtPosition(pos)
if old_item is not None and old_item.IsWindow():
old_item.GetWindow().Hide()
sizer.Detach(old_item.GetWindow())
sizer.Add(item, pos=pos, span=span, flag=flag, border=border)
def on_available_shape_double_click(self, event):
shape = event.GetEventObject().GetName()
self.available_shapes.remove(shape)
self.selected_shapes.append(shape)
self.display_images()
def on_selected_shape_double_click(self, event):
shape = event.GetEventObject().GetName()
self.selected_shapes.remove(shape)
self.available_shapes.append(shape)
self.display_images()
def on_cmd_reset_click(self, event):
self.initialise()
def on_cmd_cancel_click(self, event):
self.Destroy()
class ShapeSizer(wx.Panel):
def __init__(self, parent, shapes, shape_type):
wx.Panel.__init__(self, parent, id = wx.ID_ANY)
if shape_type == parent.shape_types['available']:
size = 40
action = parent.on_available_shape_double_click
else:
size = 80
action = parent.on_selected_shape_double_click
panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
shapes.sort()
for shape in shapes:
bitmap = wx.Bitmap(shape + '.png', wx.BITMAP_TYPE_PNG)
bitmap = self.scale_bitmap(bitmap, size, size)
img = wx.StaticBitmap(self, wx.ID_ANY, bitmap, name=shape)
img.Bind(wx.EVT_LEFT_DCLICK, action)
panel_sizer.Add(img, flag=wx.RIGHT, border=10)
self.SetSizer(panel_sizer)
#staticmethod
def scale_bitmap(bitmap, width, height):
image = wx.ImageFromBitmap(bitmap)
image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH)
result = wx.BitmapFromImage(image)
return result
if __name__ == '__main__':
app = wx.App()
ImageSizer(None, title='Image Sizer')
app.MainLoop()
Here are the images:

Every time you double click on a shape your program is creating new instances of the panels and their wx.StaticBitmap widgets, it is these new instances you are seeing as they are initially created with a small default size and then they are repositioned by the next layout. Instead you should reorganize things so you only create the set of panels once, and as the state of the shape selections changes you can have the existing panels update themselves. That will greatly reduce the flicker visible to the user.

Related

Tile based lighting system 2d

I am looking for a tile based lighting system for my tile based game. I have not tried anything because I can't think of an effective way to do this. I have searched stack overflow and I found this but its not what I want. I am making a 2d version of Minecraft with pygame.
here is my tile class
class tile():
def __init__(self, block_category, block_type, x, y, world, win):
self.x, self.y, self.width, self.height = (x*64), (y*64), 64, 64
self.block_type = block_type
self.light_level = 1 # i want light level to range from 0-1
self._image = None
self.world = world
self.win = win
self.posx, self.posy = x, y
try:
self._image = self.world.block_textures[block_category][block_type]
except:
self._image = self.world.block_textures["missing"]["missing_texture"]
self.image = self._image
def draw(self):
#draw code here self.posx, self.win, self.world and self.posy are used here if you are wondering
def change_block(self, block_category, block_type):
try:
self._image = self.world.block_textures[block_category][block_type]
except:
self._image = self.world.block_textures["missing"]["missing_texture"]
self.image = self._image
and my world data looks like this
def generate_world(self):
for x in range(0, self.width):
self.tiles[x] = {}
for y in range(0, self.height):
self.tiles[x][y] = tile("terrain", "air", x, y, self, self.win)
for x in range(0, self.width):
for y in range(0, self.height):
if y == 0:
self.tiles[x][y].change_block("terrain", "bedrock")
elif y == 38:
self.tiles[x][y].change_block("terrain", "grass_block")
elif y < 38 and y > 34:
self.tiles[x][y].change_block("terrain", "dirt")
elif y < 35 and y > 0:
self.tiles[x][y].change_block("terrain", "stone")
if x == 0 or x == self.height - 1:
self.tiles[x][y].change_block("terrain", "bedrock")
return self.tiles
my game looks like this
For 2D games like you're making, how we could apply lighting - more like, shadowing - could go into 2 options:
Change screen color to shadow color & set transparency to objects, as OP suggested
Sandwich entire thing between screen and light layer
Let's start with problem of 1st option:
Problem of setting transparency
Here's demo code based on your idea:
"""
Demonstration of color overlapping
"""
import pygame as pg
class Player(pg.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.image = pg.Surface((50, 50))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
# setting alpha on player
self.image.set_alpha(125)
def update(self, *args, **kwargs):
x, y = pg.mouse.get_pos()
c_x, c_y = self.rect.center
self.rect.move_ip(x - c_x, y - c_y)
def mainloop():
player = Player()
screen = pg.display.set_mode((500, 500))
circle_colors = (255, 0, 0), (0, 255, 0), (0, 0, 255)
circle_coords = (150, 250), (250, 250), (350, 250)
# make surface, set alpha then draw circle
bg_surfaces = []
for (color, center) in zip(circle_colors, circle_coords):
surface = pg.Surface((500, 500), pg.SRCALPHA, 32)
surface.convert_alpha()
surface.set_alpha(125)
pg.draw.circle(surface, color, center, 75)
bg_surfaces.append(surface)
running = True
while running:
screen.fill((0, 0, 0))
# draw background
for surface in bg_surfaces:
screen.blit(surface, surface.get_rect())
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
player.update()
screen.blit(player.image, player.rect)
pg.display.flip()
if __name__ == '__main__':
pg.init()
mainloop()
pg.quit()
As you see, now the player (White square)'s color is Mixed with background circles.
It's basically just like what the drawing program does with layers.
Set layer transparency 50% and stack - everything mixes, producing undesirable effect which is far from lighting effect you wanted.
Unless you want Creeper or Steve to blend with the background and become a ghosty figure, it's better to go for sandwiched layout.
Sandwiched Layout
Following is demo code which uses mouse position as light source position.
Rendering order is Ground > Player > light overlay(shadow)
Demo code:
"""
Code demonstration for https://stackoverflow.com/q/72610504/10909029
Written on Python 3.10 (Using Match on input / event dispatching)
"""
import math
import random
import itertools
from typing import Dict, Tuple, Sequence
import pygame as pg
class Position:
"""Namespace for size and positions"""
tile_x = 20
tile_size = tile_x, tile_x
class SpriteGroup:
"""Namespace for sprite groups, with chain iterator keeping the order"""
ground = pg.sprite.Group()
entities = pg.sprite.Group()
light_overlay = pg.sprite.Group()
#classmethod
def all_sprites(cls):
return itertools.chain(cls.ground, cls.entities, cls.light_overlay)
class Player(pg.sprite.Sprite):
"""Player class, which is merely a rect following pointer in this example."""
def __init__(self):
super(Player, self).__init__()
self.image = pg.Surface((50, 50))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
SpriteGroup.entities.add(self)
self.rect.move_ip(225, 225)
def update(self, *args, **kwargs):
pass
# Intentionally disabling mouse following code
# x, y = pg.mouse.get_pos()
# c_x, c_y = self.rect.center
# self.rect.move_ip(x - c_x, y - c_y)
class TileLightOverlay(pg.sprite.Sprite):
"""
Light overlay tile. Using separate sprites, so we don't have to blit on
every object above ground that requires lighting.
"""
# light lowest boundary
lighting_lo = 255
# light effect radius
light_radius = Position.tile_x * 8
def __init__(self, x, y):
super(TileLightOverlay, self).__init__()
self.image = pg.Surface(Position.tile_size)
self.image.fill((0, 0, 0))
self.rect = self.image.get_rect()
self.rect.move_ip(x * Position.tile_x, y * Position.tile_x)
SpriteGroup.light_overlay.add(self)
def update(self, *args, **kwargs):
self.image.set_alpha(self.brightness)
#property
def brightness(self):
"""Calculate distance between mouse & apply light falloff accordingly"""
distance = math.dist(self.rect.center, pg.mouse.get_pos())
if distance > self.light_radius:
return self.lighting_lo
return (distance / self.light_radius) * self.lighting_lo
class TileGround(pg.sprite.Sprite):
"""Ground tile representation. Not much is going on here."""
def __init__(self, x, y, tile_color: Sequence[float]):
super(TileGround, self).__init__()
self.image = pg.Surface(Position.tile_size)
self.image.fill(tile_color)
self.rect = self.image.get_rect()
self.rect.move_ip(x * Position.tile_x, y * Position.tile_x)
SpriteGroup.ground.add(self)
# create and keep its pair light overlay tile.
self.light_tile = TileLightOverlay(x, y)
class World:
"""World storing ground tile data."""
# tile type storing color etc. for this example only have color.
tile_type: Dict[int, Tuple[float, float, float]] = {
0: (56, 135, 93),
1: (36, 135, 38),
2: (135, 128, 56)
}
def __init__(self):
# coord system : +x → / +y ↓
# generating random tile data
self.tile_data = [
[random.randint(0, 2) for _ in range(25)]
for _ in range(25)
]
# generated tiles
self.tiles = []
def generate(self):
"""Generate world tiles"""
for x, row in enumerate(self.tile_data):
tiles_row = [TileGround(x, y, self.tile_type[col]) for y, col in enumerate(row)]
self.tiles.append(tiles_row)
def process_input(event: pg.event.Event):
"""Process input, in case you need it"""
match event.key:
case pg.K_ESCAPE:
pg.event.post(pg.event.Event(pg.QUIT))
case pg.K_UP:
pass
# etc..
def display_fps_closure(screen: pg.Surface, clock: pg.time.Clock):
"""FPS display"""
font_name = pg.font.get_default_font()
font = pg.font.Font(font_name, 10)
color = (0, 255, 0)
def inner():
text = font.render(f"{int(clock.get_fps())} fps", True, color)
screen.blit(text, text.get_rect())
return inner
def mainloop():
# keeping reference of method/functions to reduce access overhead
fetch_events = pg.event.get
display = pg.display
# local variable setup
screen = display.set_mode((500, 500))
player = Player()
world = World()
world.generate()
clock = pg.time.Clock()
display_fps = display_fps_closure(screen, clock)
running = True
# main loop
while running:
screen.fill((0, 0, 0))
# process event
for event in fetch_events():
# event dispatch
match event.type:
case pg.QUIT:
running = False
case pg.KEYDOWN:
process_input(event)
# draw in ground > entities > light overlay order
for sprite in SpriteGroup.all_sprites():
sprite.update()
screen.blit(sprite.image, sprite.rect)
# draw fps - not related to question, was lazy to remove & looks fancy
clock.tick()
display_fps()
display.flip()
if __name__ == '__main__':
pg.init()
pg.font.init()
mainloop()
pg.quit()
You'll see it's blending properly with shadow without mixing color with ground tiles.
There could be much better approach or ways to implement this - as I never used pygame before, there would be bunch of good/better stuffs I didn't read on document.
But one thing for sure - always approach your goal with mindset that everything is related to your problem until you reach the goal! Comment you thought it wasn't going to be helpful gave me idea for this design.
One option is a black background, then I use set_alpha() to set how light or dark the tile is (how much the black background is seen through the tile) and no overlay is needed. Thanks to #jupiterbjy's original answer for inspiration.

How To drag an image object in WxPython GUI?

In below code, I have image shaped transparent window and a image inside of it, I would like to move the image(screw photo)by mouse. I wrote a bind function for that screw image but it does not move? what might be the problem?
As it can be seen I added images and bind functions. Is there a missing logic?
import wx
from wx import *
import wx.lib.statbmp as sb
from io import StringIO
# Create a .png image with something drawn on a white background
# and put the path to it here.
IMAGE_PATH = './wood.png'
class ShapedFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Shaped Window",
style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER | wx.STAY_ON_TOP)
self.hasShape = False
self.delta = wx.Point(0,0)
# Load the image
image = wx.Image(IMAGE_PATH, wx.BITMAP_TYPE_PNG)
image.SetMaskColour(255,255,255)
image.SetMask(True)
self.bmp = wx.Bitmap(image)
self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight()))
dc = wx.ClientDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
self.SetWindowShape()
self.Bind(wx.EVT_RIGHT_UP, self.OnExit)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape)
panel = MyPanel(parent=self)
def SetWindowShape(self, evt=None):
r = wx.Region(self.bmp)
self.hasShape = self.SetShape(r)
def OnDoubleClick(self, evt):
if self.hasShape:
self.SetShape(wx.Region())
self.hasShape = False
else:
self.SetWindowShape()
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
def OnExit(self, evt):
self.Close()
def OnLeftDown(self, evt):
self.CaptureMouse()
pos = self.ClientToScreen(evt.GetPosition())
origin = self.GetPosition()
self.delta = wx.Point(pos.x - origin.x, pos.y - origin.y)
def OnMouseMove(self, evt):
if evt.Dragging() and evt.LeftIsDown():
pos = self.ClientToScreen(evt.GetPosition())
newPos = (pos.x - self.delta.x, pos.y - self.delta.y)
self.Move(newPos)
def OnLeftUp(self, evt):
if self.HasCapture():
self.ReleaseMouse()
class MyPanel(wx.Panel):
# A panel is a window on which controls are placed. (e.g. buttons and text boxes)
# wx.Panel class is usually put inside a wxFrame object. This class is also inherited from wxWindow class.
def __init__(self,parent):
super().__init__(parent=parent)
MyImage(self)
class MyImage(wx.StaticBitmap):
def __init__(self,parent):
super().__init__(parent=parent)
jpg1 = wx.Image('./Images/screwsmall.png', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
# bitmap upper left corner is in the position tuple (x, y) = (5, 5)
self.myImage = wx.StaticBitmap(parent, -1, jpg1, (10 + jpg1.GetWidth(), 5), (jpg1.GetWidth(), jpg1.GetHeight()))
self.myImage.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.myImage.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
def OnMouseMove(self, evt):
if evt.Dragging() and evt.LeftIsDown():
pos = self.ClientToScreen(evt.GetPosition())
newPos = (pos.x - self.delta.x, pos.y - self.delta.y)
self.Move(newPos)
def OnLeftUp(self, evt):
if self.HasCapture():
self.ReleaseMouse()
def OnLeftDown(self, evt):
self.CaptureMouse()
pos = self.ClientToScreen(evt.GetPosition())
origin = self.GetPosition()
self.delta = wx.Point(pos.x - origin.x, pos.y - origin.y)
if __name__ == '__main__':
app = wx.App()
ShapedFrame().Show()
app.MainLoop()
Visual output of my code you can use different shapes in local directory. To install wxpython for 3.x you can check this link https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04/ download your version for ubuntu and use pip install command.
I am going to put my answer. As it can be seen in the #RolfofSaxony comment, there is a drag image demo inside the WXPYTHON tar file. In that DragImage.py file there are two different classes do the dragging job. I modified those functions and wrote my own two class. You can use these classes in your code as a component. My code is working and tested.
class DragShape:
def __init__(self, bmp):
self.bmp = bmp
self.pos = (0,0)
self.shown = True
self.text = None
self.fullscreen = False
def HitTest(self, pt):
rect = self.GetRect()
return rect.Contains(pt)
def GetRect(self):
return wx.Rect(self.pos[0], self.pos[1],
self.bmp.GetWidth(), self.bmp.GetHeight())
def Draw(self, dc, op = wx.COPY):
if self.bmp.IsOk():
memDC = wx.MemoryDC()
memDC.SelectObject(self.bmp)
dc.Blit(self.pos[0], self.pos[1],
self.bmp.GetWidth(), self.bmp.GetHeight(),
memDC, 0, 0, op, True)
return True
else:
return False
#----------------------------------------------------------------------
class DragCanvas(wx.ScrolledWindow):
def __init__(self, parent, ID):
wx.ScrolledWindow.__init__(self, parent, ID)
self.shapes = []
self.dragImage = None
self.dragShape = None
self.hiliteShape = None
self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
bmp = images.TheKid.GetBitmap()
shape = DragShape(bmp)
shape.pos = (200, 5)
self.shapes.append(shape)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
# We're not doing anything here, but you might have reason to.
# for example, if you were dragging something, you might elect to
# 'drop it' when the cursor left the window.
def OnLeaveWindow(self, evt):
pass
# Go through our list of shapes and draw them in whatever place they are.
def DrawShapes(self, dc):
for shape in self.shapes:
if shape.shown:
shape.Draw(dc)
# This is actually a sophisticated 'hit test', but in this
# case we're also determining which shape, if any, was 'hit'.
def FindShape(self, pt):
for shape in self.shapes:
if shape.HitTest(pt):
return shape
return None
# Fired whenever a paint event occurs
def OnPaint(self, evt):
dc = wx.PaintDC(self)
self.PrepareDC(dc)
self.DrawShapes(dc)
# print('OnPaint')
# Left mouse button is down.
def OnLeftDown(self, evt):
# Did the mouse go down on one of our shapes?
shape = self.FindShape(evt.GetPosition())
# If a shape was 'hit', then set that as the shape we're going to
# drag around. Get our start position. Dragging has not yet started.
# That will happen once the mouse moves, OR the mouse is released.
if shape:
self.dragShape = shape
self.dragStartPos = evt.GetPosition()
# Left mouse button up.
def OnLeftUp(self, evt):
if not self.dragImage or not self.dragShape:
self.dragImage = None
self.dragShape = None
return
# Hide the image, end dragging, and nuke out the drag image.
self.dragImage.Hide()
self.dragImage.EndDrag()
self.dragImage = None
if self.hiliteShape:
self.RefreshRect(self.hiliteShape.GetRect())
self.hiliteShape = None
# reposition and draw the shape
# Note by jmg 11/28/03
# Here's the original:
#
# self.dragShape.pos = self.dragShape.pos + evt.GetPosition() - self.dragStartPos
#
# So if there are any problems associated with this, use that as
# a starting place in your investigation. I've tried to simulate the
# wx.Point __add__ method here -- it won't work for tuples as we
# have now from the various methods
#
# There must be a better way to do this :-)
#
self.dragShape.pos = (
self.dragShape.pos[0] + evt.GetPosition()[0] - self.dragStartPos[0],
self.dragShape.pos[1] + evt.GetPosition()[1] - self.dragStartPos[1]
)
self.dragShape.shown = True
self.RefreshRect(self.dragShape.GetRect())
self.dragShape = None
# The mouse is moving
def OnMotion(self, evt):
# Ignore mouse movement if we're not dragging.
if not self.dragShape or not evt.Dragging() or not evt.LeftIsDown():
return
# if we have a shape, but haven't started dragging yet
if self.dragShape and not self.dragImage:
# only start the drag after having moved a couple pixels
tolerance = 2
pt = evt.GetPosition()
dx = abs(pt.x - self.dragStartPos.x)
dy = abs(pt.y - self.dragStartPos.y)
if dx <= tolerance and dy <= tolerance:
return
# refresh the area of the window where the shape was so it
# will get erased.
self.dragShape.shown = False
self.RefreshRect(self.dragShape.GetRect(), True)
self.Update()
item = self.dragShape.text if self.dragShape.text else self.dragShape.bmp
self.dragImage = wx.DragImage(item,
wx.Cursor(wx.CURSOR_HAND))
hotspot = self.dragStartPos - self.dragShape.pos
self.dragImage.BeginDrag(hotspot, self, self.dragShape.fullscreen)
self.dragImage.Move(pt)
self.dragImage.Show()
# if we have shape and image then move it, posibly highlighting another shape.
elif self.dragShape and self.dragImage:
onShape = self.FindShape(evt.GetPosition())
unhiliteOld = False
hiliteNew = False
# figure out what to hilite and what to unhilite
if self.hiliteShape:
if onShape is None or self.hiliteShape is not onShape:
unhiliteOld = True
if onShape and onShape is not self.hiliteShape and onShape.shown:
hiliteNew = True
# if needed, hide the drag image so we can update the window
if unhiliteOld or hiliteNew:
self.dragImage.Hide()
if unhiliteOld:
dc = wx.ClientDC(self)
self.hiliteShape.Draw(dc)
self.hiliteShape = None
if hiliteNew:
dc = wx.ClientDC(self)
self.hiliteShape = onShape
self.hiliteShape.Draw(dc, wx.INVERT)
# now move it and show it again if needed
self.dragImage.Move(evt.GetPosition())
if unhiliteOld or hiliteNew:
self.dragImage.Show()

Aligning QGridLayout rows in QScrollArea

I am trying to create a lot of rows in a PyQt5 grid widget, but they try to expand as much as they can. How can I set a fixed cell height? They are represented like this:
But I would like them to stick at the top, ordered like this:
Code:
name = QtWidgets.QLabel()
name.setText(str(ui.nombre.toPlainText()) + "({}, {}, {})".format(do, cota, alejamiento))
borrar = QtWidgets.QPushButton()
borrar.setText("X")
borrar.clicked.connect(self.borrar)
ui.elementos.addWidget(name, self.num_elementos, 0, 1, 1)
ui.elementos.addWidget(borrar, self.num_elementos, 1, 1, 1)
self.num_elementos += 1
self.update()
print(self.puntos)
And the elementos widget is created in other class:
self.scroll = QtWidgets.QScrollArea(self.gridLayoutWidget_2)
self.scroll_widget = QtWidgets.QWidget()
self.scroll_widget.resize(200, 700)
self.elementos = QtWidgets.QGridLayout()
self.scroll_widget.setLayout(self.elementos)
self.scroll.setWidget(self.scroll_widget)
self.Punto.addWidget(self.scroll, 4, 0, 1, 3)
You need to add a stretchable space beneath the rows of widgets, so that it pushes them all up to the top. One way to do this is to put another widget inside the scroll-widget, and then use a vertical layout to add the spacer. It will also help if you make the scroll-widget resizable, otherwise the rows will start to get squashed if too many are added.
Below is a demo that implements all that. Hopefully it should be clear how you can adapt this to work with your own code:
import sys
from PyQt5 import QtCore, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.scroll = QtWidgets.QScrollArea()
self.scroll.setWidgetResizable(True)
self.scroll_widget = QtWidgets.QWidget()
self.scroll_widget.setMaximumWidth(200)
self.elementos_widget = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.scroll_widget)
vbox.setContentsMargins(0, 0, 0, 0)
vbox.addWidget(self.elementos_widget)
vbox.addStretch()
self.elementos = QtWidgets.QGridLayout()
self.elementos_widget.setLayout(self.elementos)
self.scroll.setWidget(self.scroll_widget)
self.button = QtWidgets.QPushButton('Add')
self.button.clicked.connect(self.crear_punto)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.scroll)
layout.addWidget(self.button)
def crear_punto(self):
num_elementos = self.elementos.rowCount()
name = QtWidgets.QLabel()
name.setText('FOO %s' % num_elementos)
borrar = QtWidgets.QPushButton()
borrar.setText('X')
self.elementos.addWidget(name, num_elementos, 0)
self.elementos.addWidget(borrar, num_elementos, 1)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 100, 300, 500)
window.show()
sys.exit(app.exec_())

wxPython: How to make Dialog resize according to the content of the embedded StaticText

This is my small program for testing:
import wx
class Test(wx.Dialog):
def __init__(self, parent, title="", caption='', btnList=['OK']):
wx.Dialog.__init__(self, parent, -1, title=title)
self.btnList = btnList
self.parent = parent
sizer = wx.BoxSizer()
self.panel = wx.Panel(self, -1)
sizer.Add(self.panel)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.panel.SetSizer(self.mainSizer)
self.caption = caption
self.setCaption()
self.setButton()
self.SetSizerAndFit(sizer)
def setCaption(self):
caplb = wx.StaticText(self.panel, -1, self.caption)
self.mainSizer.Add(caplb, 1, wx.EXPAND | wx.ALL, 10)
capFont = caplb.GetFont()
capFont.SetPointSize(10)
capFont.SetWeight(wx.FONTWEIGHT_BOLD)
caplb.SetFont(capFont)
caplb.SetForegroundColour(wx.RED)
def setButton(self):
self.btnBox = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(self.btnBox, 0, wx.EXPAND | wx.BOTTOM, 10)
self.btnBox.Add((1, 1), 1, wx.EXPAND)
self.btns = []
for i in range(len(self.btnList)):
self.btns.append(wx.Button(self.panel, -1, self.btnList[i]))
self.btnBox.Add(self.btns[i])
self.btnBox.Add((1, 1), 1, wx.EXPAND)
if __name__ == "__main__":
app = wx.App()
msg = ""
for i in range(8):
msg += "%s: ATTENTION! ATTENTION! ATTENTION! ATTENTION!\n" % i
dlg = Test(None, caption=msg, title="WARNING", btnList=['Yes', 'No'])
dlg.CenterOnParent()
val = dlg.ShowModal()
app.MainLoop()
I want the dialog to spread so that all text in the caplb can be seen. In most of the systems I has worked on this piece of code work well. But when I move to the new system, there is something with the gtk limit to show only half of the caplb. I don't know how to force it to show all of the StaticText. Can you please have a look at that and show me the hint? Thanks ahead!!!

PyQt4 video player crashes when moving window

I've written a simple PyQt4 GUI that plays an OpenCV VideoCapture. This requires converting frames from numpy arrays to QImages. I'm using OpenCV so that I can detect circles using my findCircles method.
However, when I pass my frames to findCircles, the program crashes when the window is moved. This problem does not occur when I don't search for circles. I don't understand why this is happening, as I'm under the impression that the work is being done on a different thread than the GUI since I call findCircles from the run method of a QThread.
Note that I don't receive a normal error message in the console; Python crashes like such:
Here is the video file I've been using to test my player. I'm running Python 2.7.6 on Windows 8.1.
import sys
import cv2.cv as cv, cv2
from PyQt4.Qt import *
import time
def numpyArrayToQImage(array):
if array != None:
height, width, bytesPerComponent = array.shape
bytesPerLine = bytesPerComponent * width;
cv2.cvtColor(array, cv.CV_BGR2RGB, array)
return QImage(array.data, width, height, bytesPerLine, QImage.Format_RGB888)
return None
def findCircles(frame):
grayFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blurredFrame = cv2.medianBlur(grayFrame, 3)
circles = cv2.HoughCircles(blurredFrame, cv.CV_HOUGH_GRADIENT, 1, 30, param1=50, param2=30, minRadius=30, maxRadius=35)
if circles is not None:
for i in circles[0]:
cv2.circle(frame, (i[0], i[1]), i[2], (255, 0, 0), 1) # Perimeter
cv2.circle(frame, (i[0], i[1]), 3, (0, 255, 0), -1) # Center
class VideoThread(QThread):
frameProcessed = pyqtSignal(QImage)
def __init__(self, video, videoLabel):
QThread.__init__(self)
self.video = video
self.fps = self.video.get(cv.CV_CAP_PROP_FPS)
self.frameCount = self.video.get(cv.CV_CAP_PROP_FRAME_COUNT)
self.startingSecond = 0
self.videoLabel = videoLabel
def run(self):
clockAtStart = time.clock()
while True:
runtime = self.startingSecond + (time.clock() - clockAtStart)
currentFrame = int(runtime * self.fps)
if currentFrame < self.frameCount - 1:
self.video.set(cv.CV_CAP_PROP_POS_FRAMES, currentFrame)
frame = self.video.read()[1]
findCircles(frame) # Removing this line removes the issue
self.frameProcessed.emit(numpyArrayToQImage(frame))
time.sleep(.02)
else:
break
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
#pyqtSlot(QImage)
def updateVideoLabel (self, image):
self.videoLabel.setPixmap(QPixmap.fromImage(image))
self.videoLabel.update()
def initUI(self):
self.setGeometry(300, 300, 500, 375)
self.setMinimumHeight(250)
self.createWidgets()
self.addWidgets()
def startNewVideo(self):
self.video = cv2.VideoCapture(unicode(QFileDialog.getOpenFileName(self, "Open video").toUtf8(), encoding="UTF-8"))
self.videoThread = VideoThread(self.video, self.videoLabel)
self.videoThread.frameProcessed.connect(self.updateVideoLabel)
self.playVideoFrom(0)
def playVideoFrom(self, frame):
self.videoThread.startingSecond = frame / self.videoThread.fps
self.videoThread.start()
def createWidgets(self):
self.populateMenuBar()
self.videoLabel = QLabel()
self.videoLabel.setStyleSheet('background-color : black;');
def populateMenuBar(self):
self.menuBar = self.menuBar()
fileMenu = QMenu('File', self)
openAction = QAction('Open video...', self)
openAction.triggered.connect(self.startNewVideo)
fileMenu.addAction(openAction)
self.menuBar.addMenu(fileMenu)
def addWidgets(self):
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.videoLabel, 1)
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
centralWidget.setLayout(mainLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
player = MainWindow()
player.show()
sys.exit(app.exec_())
I've tested your program, and it crashes when it finds no circles as indicated in the error message:
Traceback (most recent call last):
File "test_opencv_tkinter.py", line 53, in run
findCircles(frame) # Removing this line removes the issue
File "test_opencv_tkinter.py", line 26, in findCircles
if len(circles) > 0:
TypeError: object of type 'NoneType' has no len()
I've made some changes in the findCircles(frame) function, as follows, and it runs without error, even when I move the window around on the screen.
def findCircles(frame):
grayFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blurredFrame = cv2.medianBlur(grayFrame, 3)
circles = cv2.HoughCircles(grayFrame,cv.CV_HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)
if circles == None:
print "no circles found"
return
if len(circles) > 0:
print "found circles ", len(circles[0])
for i in circles[0]:
cv2.circle(frame, (i[0], i[1]), i[2], (255, 0, 0), 1) # Perimeter
cv2.circle(frame, (i[0], i[1]), 3, (0, 255, 0), -1) # Center

Resources