yltang
yltang

Reputation: 21

How to create a transparent frame with visible borders?

I would like to create a transparent frame with visible borders. The code I've written based on hasenj's work of 'shaped-frame' (http://hasenj.wordpress.com/2009/04/14/making-a-fancy-window-in-wxpython/) is as follows. But I still have problem making one. I must have missed something. Could anybody point out what's wrong with the program? Thanks.


import wx

class FancyFrame(wx.Frame):
    def __init__(self, width, height):
        wx.Frame.__init__(self, None, style = wx.STAY_ON_TOP | 
                          wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED,
                          size=(width, height))
        self.SetTransparent(50)
        b = wx.EmptyBitmap(width, height)
        dc = wx.MemoryDC()
        dc.SelectObject(b)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.SetPen(wx.Pen('red', 2))
        dc.DrawRectangle(0, 0, width, height)
        dc.SelectObject(wx.NullBitmap)
        self.SetShape(wx.RegionFromBitmap(b))
        self.Bind(wx.EVT_KEY_UP, self.OnKeyDown)
        self.Show(True)

    def OnKeyDown(self, event):
        """quit if user pressEsc"""
        if event.GetKeyCode() == 27: #27 is the Esc key
            self.Close(force=True)
        else:
            event.Skip()

if __name__ == "__main__":
    app = wx.App()
    FancyFrame(300, 300)
    app.MainLoop()

Upvotes: 2

Views: 1668

Answers (2)

Fenikso
Fenikso

Reputation: 9451

It is not very good idea to draw stuff outside MainLoop. You should setup your wx application so it is Event driven. The events EVT_PAINT and EVT_SIZE should be handled. The wx.Timer can be used for basic movement.

import wx

#============================================================================= 
class DrawPanelDB(wx.Panel):
    def __init__(self, *args, **kwargs):
        wx.Panel.__init__(self, *args, **kwargs)

        self.ballPosition = [20, 20]
        self.ballDelta = [1, 1]

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTime, self.timer)
        self.timer.Start(20)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)

    #-------------------------------------------------------------------------
    def OnPaint(self, event):
        dc = wx.BufferedPaintDC(self)
        dc.SetBackground(wx.Brush(wx.BLACK))
        dc.Clear()
        dc.DrawCirclePoint(self.ballPosition, 20)

    #-------------------------------------------------------------------------
    def OnSize(self, event):
        self.Refresh()
        self.Update()

    #-------------------------------------------------------------------------
    def OnEraseBackground(self, event):
        pass # Or None  

    #-------------------------------------------------------------------------
    def OnTime(self, event):
        self.ballPosition[0] += self.ballDelta[0]
        self.ballPosition[1] += self.ballDelta[1]
        w, h = self.GetClientSizeTuple()
        if self.ballPosition[0] > w:
            self.ballPosition[0] = w
            self.ballDelta[0] *= -1
        if self.ballPosition[1] > h:
            self.ballPosition[1] = h
            self.ballDelta[1] *= -1
        if self.ballPosition[0] < 0:
            self.ballPosition[0] = 0
            self.ballDelta[0] *= -1
        if self.ballPosition[1] < 0:
            self.ballPosition[1] = 0
            self.ballDelta[1] *= -1       
        self.Refresh()

#============================================================================= 
class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        self.panel = DrawPanelDB(self)
        self.Show()

#============================================================================= 
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

Edit: Example with ScreenDC using xor function for non-destructible drawing.

import wx

#============================================================================= 
class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.ballPosition = [20, 20]
        self.lastPosition = None
        self.ballDelta = [1, 1]

        self.timer = wx.Timer(self)        
        self.Bind(wx.EVT_TIMER, self.OnTime, self.timer)
        self.timer.Start(20)

    #-------------------------------------------------------------------------
    def OnTime(self, event):
        self.ballPosition[0] += self.ballDelta[0]
        self.ballPosition[1] += self.ballDelta[1]
        w, h = wx.DisplaySize()
        if self.ballPosition[0] > w:
            self.ballPosition[0] = w
            self.ballDelta[0] *= -1
        if self.ballPosition[1] > h:
            self.ballPosition[1] = h
            self.ballDelta[1] *= -1
        if self.ballPosition[0] < 0:
            self.ballPosition[0] = 0
            self.ballDelta[0] *= -1
        if self.ballPosition[1] < 0:
            self.ballPosition[1] = 0
            self.ballDelta[1] *= -1       

        dc = wx.ScreenDC()
        dc.StartDrawingOnTop()
        dc.SetLogicalFunction(wx.XOR) 
        if self.lastPosition is not None:
            dc.DrawRectangle(self.lastPosition[0], self.lastPosition[1], 15, 15)
        self.lastPosition = (self.ballPosition[0], self.ballPosition[1])
        dc.DrawRectangle(self.ballPosition[0], self.ballPosition[1], 15, 15)    
        dc.EndDrawingOnTop()

#============================================================================= 
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

Edit: Another possibility is to keep backup copy of that part of the screen.

import wx

#============================================================================= 
class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.ballPosition = [20, 20]
        self.lastPosition = None
        self.ballDelta = [1, 1]
        self.patch = wx.EmptyBitmap(20, 20)

        self.timer = wx.Timer(self)        
        self.Bind(wx.EVT_TIMER, self.OnTime, self.timer)
        self.timer.Start(20)

    #-------------------------------------------------------------------------
    def OnTime(self, event):
        self.ballPosition[0] += self.ballDelta[0]
        self.ballPosition[1] += self.ballDelta[1]
        w, h = wx.DisplaySize()
        if self.ballPosition[0] > w:
            self.ballPosition[0] = w
            self.ballDelta[0] *= -1
        if self.ballPosition[1] > h:
            self.ballPosition[1] = h
            self.ballDelta[1] *= -1
        if self.ballPosition[0] < 0:
            self.ballPosition[0] = 0
            self.ballDelta[0] *= -1
        if self.ballPosition[1] < 0:
            self.ballPosition[1] = 0
            self.ballDelta[1] *= -1       

        dc = wx.ScreenDC()
        dc.StartDrawingOnTop()

        bdc = wx.MemoryDC(self.patch)
        if self.lastPosition is not None:
            dc.Blit(self.lastPosition[0] - 2, self.lastPosition[1] - 2, 20, 20, bdc, 0, 0)
        bdc.Blit(0, 0, 20, 20, dc, self.ballPosition[0] - 2, self.ballPosition[1] - 2)
        self.lastPosition = (self.ballPosition[0], self.ballPosition[1])

        dc.DrawRectangle(self.ballPosition[0], self.ballPosition[1], 15, 15)    
        dc.EndDrawingOnTop()

#============================================================================= 
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

Upvotes: 2

Ryan Ginstrom
Ryan Ginstrom

Reputation: 14121

I would recommend creating a transparent window (window.SetTransparent(0)), and placing a bitmap on it that you draw to yourself (see this answer for example).

Then you would set an event handler for mouse move, and move around the window with the cursor. Also set a timer, so that you can fade out the bitmap if the mouse doesn't move for x seconds.

Upvotes: 0

Related Questions