2

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()

yltang
  • 21
  • 3

2 Answers2

2

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()
Fenikso
  • 9,251
  • 5
  • 44
  • 72
  • Thanks for the suggestion. Well, I do need to draw *on the screen* because I am implementing a virtual mouse. What I intend to do is to have the program repeatedly draw rectangles or lines on the screen until the user press the button of a special one-key device (this is for hand-disabled people), which signifies that the user want to move the mouse to the position on the screen. – yltang Apr 22 '11 at 13:34
  • @yltang - I have added another example with drawing on screen instead of window. I did not find a way how to invalidate part of screen to force it redrawn however, so I had to use xor function. – Fenikso Apr 26 '11 at 07:03
0

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.

Community
  • 1
  • 1
Ryan Ginstrom
  • 13,915
  • 5
  • 45
  • 60
  • Thanks Ryan. I've written some code based on hasenj's work. However, I still have problem showing red borders on a transparent rectangle. Since the system doesn't let me paste the whole program (about 30 lines) here. Is it possible that I send my program to you and you might check what's wrong with it? Thanks. – yltang Apr 25 '11 at 08:30
  • Sorry, but the system is weird. If I type the the "enter" key, my message will be saved and posted, just the same as if I click the "Add comment" (or "Save Edits") button. Hence, I can't add blank lines. Moreover, if I copy my program and paste it here (the system does let me paste), nothing happens if I click the "Add comment" (or "Save Edits") button -- I can't post the message. What can I do? – yltang Apr 26 '11 at 05:50
  • @yltang - I did not mean to put the code in a comment comment, but edit your question on the top. – Fenikso Apr 26 '11 at 05:59