import wx
class DrawPanel(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,title="Draw",size=(150,150))
self.SetTransparent(0)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event=None):
dc = wx.PaintDC(self)
dc.SetPen(wx.Pen(wx.BLACK, 5))
# Draw a line
dc.DrawLine(0, 0, 150, 150)
app = wx.App()
frame = DrawPanel()
frame.Show(True)
app.MainLoop()
Now frame and line are all invisible.
Please tell me how to make frame fully transparent, while line is visible ?
When you make the frame transparent, it makes all its children transparent too. Thus, this really isn't possible. You can make semi-transparent drawings though via GraphicsContext. And you could take a look at GLCanvas (a wrapper for OpenGL) or you might be able to cobble something together with Cairo. See the wxPython demo package (available from www.wxpython.org) for examples.
Related
OS: Windows 10
Python 3.8
I am trying to display PNG images with transparency with no titlebar, or any other indication of window info (imagine sticking a sticker on your screen). I've already done this once in another language by putting an image in a window and removing all the elements of the window, including titlebar and background, but I cannot achieve it in python.
I have already tried Tkinter, but seems like it's not designed to work with PNG transparency and it cannot have separate transparency for parent and child windows.
Now I am trying wxPython. My idea is that it might be possible to make the parent window transparent and the child window (which holds the image) opaque, but cannot figure out if that's even possible.
Here is the simplified version of my attempt:
app = wx.App()
path = 'Images/02.png'
bitmap = wx.Bitmap(path, wx.BITMAP_TYPE_PNG) # Create bitmap
frame = wx.Frame(None,-1,'Transparent Window',size=(1000,1000)) # Create frame (top level window)
frame.SetTransparent(100) # Change transparency of the frame
panel = wx.Panel(frame, -1) # Create panel (to hold the image)
wx.StaticBitmap(panel, -1, bitmap) # Put the image in the panel
panel.SetTransparent(255) # Change transparency of the panel (the image)
frame.Show()
app.MainLoop()
But unfortunately ".SetTransparent()" only works on top level window, so I can't set the transparency of the panel to anything else.
But basically, the question is, is it possible to display image with transparency on its own with Python?
I am not looking for any specific method of achieving this, the one I provided was just the only one I know. So please help me if you can :)
Edit 1:
Tried to use wx.lib.agw.advancedsplash:
import wx
import wx.lib.agw.advancedsplash as AS
app = wx.App()
frame = wx.Frame(None, -1, "AdvancedSplash Test")
imagePath = "Images/05.png"
bitmap = wx.Bitmap(imagePath)
splash = AS.AdvancedSplash(frame, bitmap=bitmap)
app.MainLoop()
But it still fills in transparent areas
You may want to look into the implementation of AdvancedSplash in wx.lib.agw.advancedsplash. It’s sort of a splash screen that adapts itself to the picture you use, and if you have transparencies they will be taken into account.
Transparency is be applied to the frame, so don't worry about the panel, the whole thing becomes transparent.
Be careful how you run this code, as it needs to be killed or Ctrl-C'd.
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, pos=(200,200), style=wx.FRAME_NO_TASKBAR)
panel1 = wx.Panel(self)
bitmap = wx.Bitmap('ace2.png', wx.BITMAP_TYPE_PNG)
image = wx.StaticBitmap(panel1, -1, bitmap)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(panel1)
self.SetSizerAndFit(sizer)
self.Show()
self.OnTransparent(None)
def OnTransparent(self, event):
self.SetTransparent(180)
self.Update()
app = wx.App()
frame = MyFrame(None, -1)
app.MainLoop()
Try something like this:
import tkinter as tk
root = tk.Tk()
root.overrideredirect(True)
root.config(bg="blue", bd=0, highlightthickness=0)
root.attributes("-transparentcolor", "blue")
canvas = tk.Canvas(root, bg="blue", bd=0, highlightthickness=0)
canvas.pack()
tk_img = tk.PhotoImage(file="cards.png")
canvas.create_image(0, 0, image=tk_img, anchor="nw")
root.mainloop()
It creates an image on the screen like this:
It changes the transparent colour of the window to "blue" (because there isn't any thing blue in the picture that I am using) and sets the background colour to "blue". That makes the window transparent. To show the image I used a tk.Canvas but it might work with tk.Label (haven't tested it).
Please note that this will most likely work only on Windows.
I'm taking the first steps to move from .NET to Python but I'm already having a few headaches regarding the GUI design.
For some reason, passing the size attribute to a wx.Button seems to be kind of ignored. And I say "kind of" because the actual space seems to change but the actual button keeps occupying the same space:
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
self.SetSize((800, 600))
self.SetTitle('Main Menu')
self.Centre()
self.Show(True)
''' Fill the form '''
self.lblUsername = wx.StaticText(self, size=(80, -1), pos=(20,20), label="Username:" )
self.txtUsername = wx.TextCtrl(self, size=(140, -1), pos=(100,20), style=wx.TE_PROCESS_ENTER)
self.lblPassword = wx.StaticText(self, size=(80, -1), pos=(20,50), label="Password:" )
self.txtPassword = wx.TextCtrl(self, size=(140, -1), pos=(100,50), style=wx.TE_PROCESS_ENTER)
self.btnOK = wx.Button( self, label="OK", pos=(260, 16), size=(50,50))
self.btnOK.Bind(wx.EVT_BUTTON, self.onClickOK)
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Ready')
def onClickOK(self, e):
print "Button triggered"
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
No matter what size I set, the Button won't stretch (it will be centered as if all the space was actually being used, but will still be small).
Can anyone spot what am I doing wrong?
This is a limit imposed by OSX. The way the native button widget is drawn only allows it to be stretched horizontally, and the vertical size is fixed. Or rather, as you've discovered, the widget itself can be larger than normal vertically, but it will only draw itself at a fixed height within that space. It seems less neccessary with modern versions of OSX, but if you look at buttons in OSX from a few years ago you can probably see why this is so. The esthetic graphical effect of the "tic-tack" or "capsule" buttons would be totally ruined if they were a non-standard vertical size, causing the images used to draw the buttons to be stretched. wxWidgets follows the native plaform look and feel standards where possible, in this case it happens that Apple's standard is imposed upon us and wx can't offer the same level of flexibility that it usually does.
You do have some options however if you really want taller than normal buttons. The native widgets have a few different standard sizes, which you can select using the SetWindowVariant method, although I don't think the variants would get as tall as you want. Or you could use a generic button widget instead of a native one, such as wx.lib.buttons.ThemedGenButton.
Same problem in my little Software EventSoundControl.
Just a workaround: Use a multiline label and sizes of wxButton will work as desired!
If you want the button to stretch when you resize the frame, then you cannot use static sizes and positioning. You will need to put your widgets in a sizer. Then the sizer will manage the position / size of the widget(s) as you change the size of the frame. There are many examples on the wxPython wiki that demonstrate how to use sizers. You might also find the following tutorial helpful:
http://zetcode.com/wxpython/layout/
Is it possible to make a wxPython window only re-sizable to a certain ratio? I know you can disable resizing; however, I'd like it so when the window was resized it stuck to a certain width to height ratio.
One obvious way to do this would be to bind wx.EVT_SIZE to a function that constrains the aspect ratio. I'm not certain this is The Right Way to do this, but it works:
import wx
class SizeEvent(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Centre()
self.SetSizeWH(400, 300)
self.Show(True)
def OnSize(self, event):
hsize = event.GetSize()[0] * 0.75
self.SetSizeHints(minW=-1, minH=hsize, maxH=hsize)
self.SetTitle(str(event.GetSize()))
app = wx.App()
SizeEvent(None, 1, 'sizeevent.py')
app.MainLoop()
(The boilerplate is borrowed from here.)
I'm not too familiar with wxPython, but can't you just reset the window size to your max/min size that you want once the user pass that? Preferably in the event that detects resizing?
import wx
class MainFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self, parent, title=title, size=(640,480))
self.mainPanel=DoubleBufferTest(self,-1)
self.Show(True)
class DoubleBufferTest(wx.Panel):
def __init__(self,parent=None,id=-1):
wx.Panel.__init__(self,parent,id,style=wx.FULL_REPAINT_ON_RESIZE)
self.SetBackgroundColour("#FFFFFF")
self.timer = wx.Timer(self)
self.timer.Start(100)
self.Bind(wx.EVT_TIMER, self.update, self.timer)
self.Bind(wx.EVT_PAINT,self.onPaint)
def onPaint(self,event):
event.Skip()
dc = wx.MemoryDC()
dc.SelectObject(wx.EmptyBitmap(640, 480))
gc = wx.GraphicsContext.Create(dc)
gc.PushState()
gc.SetBrush(wx.Brush("#CFCFCF"))
bgRect=gc.CreatePath()
bgRect.AddRectangle(0,0,640,480)
gc.FillPath(bgRect)
gc.PopState()
dc2=wx.PaintDC(self)
dc2.Blit(0,0,640,480,dc,0,0)
def update(self,event):
self.Refresh()
app = wx.App(False)
f=MainFrame(None,"Test")
app.MainLoop()
I've come up with this code to draw double buffered GraphicsContext content onto a panel, but there's a constant flickering across the window. I've tried different kinds of paths, like lines and curves but it's still there and I don't know what's causing it.
You get flicker because each Refresh() causes the background to get erased before calling onPaint. You need to bind to EVT_ERASE_BACKGROUND and make it a no-op.
class DoubleBufferTest(wx.Panel):
def __init__(self,parent=None,id=-1):
# ... existing code ...
self.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase)
def onErase(self, event):
pass
# ... existing code ...
If you're using a relatively modern wxWidgets, you can use wx.BufferedPaintDC and avoid having to muck around with the memory DC and painting and blitting on your own. Also, on windows, FULL_REPAINT_ON_RESIZE often causes flickering even when you're not resizing the window due to funny things going on under the covers - if you don't need it, going with NO_FULL_REPAINT_ON_RESIZE may help. Otherwise, you'll want to simplify your code some to make sure you can get the simplest thing to work, and perhaps take a look at the DoubleBufferedDrawing wiki page at wxpython.org.
import wx
class TestDraw(wx.Panel):
def __init__(self,parent=None,id=-1):
wx.Panel.__init__(self,parent,id)
self.SetBackgroundColour("#FFFFFF")
self.Bind(wx.EVT_PAINT,self.onPaint)
def onPaint(self, event):
event.Skip()
dc=wx.PaintDC(self)
dc.BeginDrawing()
width=dc.GetSize()[0]
height=dc.GetSize()[1]
if height<width:
self.drawTestRects(dc)
else:
dc.Clear()
dc.EndDrawing()
def drawTestRects(self,dc):
dc.SetBrush(wx.Brush("#000000",style=wx.SOLID))
dc.DrawRectangle(50,50,50,50)
dc.DrawRectangle(100,100,100,100)
class TestFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(640,480))
self.mainPanel=TestDraw(self,-1)
self.Show(True)
app = wx.App(False)
frame = TestFrame(None,"Test App")
app.MainLoop()
This code should draw the test rectangles only when the height is less than the width, and otherwise the window should remain clear. However, if you mess with resizing the window, the panel isn't actually redrawn unless it is moved off the window. What am I doing wrong?
You can bind a method to handle wx.EVT_SIZE or the panel and invalidate it there. Alternatively simply use the wx.FULL_REPAINT_ON_RESIZE for the panel.
The documentation for a SizeEvent claims that there may be some complications when drawing depends on the dimensions of the window. I do not know exactly what is going on behind the scenes. I followed the suggestion on the link and added the call self.Refresh() to the top of onPaint() and this seems to give the desired behavior. See mghie's answer for a more efficient example of working code.