Set wx.Frame size (wxPython - wxWidgets) - python

I am new to wxPython and I am finding some issues while seting a given size for both frames and windows (widgets). I have isolated the issue to the simplest case where I try to create a Frame of 250x250 pixels.
Running the code I get a window of an actual size of 295 width by 307 height (taking into consideration the Windows´s top window bar)
I am using Python 2.7 in Windows 10.
What am I missing?
#!/bin/env python
import wx
# App Class
class MyAppTest7(wx.App):
def OnInit(self):
frame = AppFrame(title = u'Hello World', pos=(50, 60), size=(250, 250))
frame.Show()
self.SetTopWindow(frame)
return True
# AppFrame
class AppFrame(wx.Frame):
def __init__(self, title, pos, size):
wx.Frame.__init__(self, parent=None, id=-1, title=title, pos=pos, size=size)
if __name__ == '__main__':
app = MyAppTest7(False)
app.MainLoop()
An addtional test to further show issue:
#!/bin/env python
import wx
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, title="The Main Frame")
self.SetTopWindow(self.frame)
self.frame.Show(True)
return True
class MyFrame(wx.Frame):
def __init__(self, parent, id=wx.ID_ANY, title="", pos=wx.DefaultPosition, size=(400,100), style=wx.DEFAULT_FRAME_STYLE, name="MyFrame"):
super(MyFrame, self).__init__(parent, id, title, pos, size, style, name)
self.panel = wx.Panel(self)
if __name__ == "__main__":
app = MyApp(False)
app.MainLoop()
And the result:
As you can see displayed window (frame) has 482 pixels (-see Paint's bottom bar-) instead of the expected 400.
Window size measured in pixels

Add this before your call to app.MainLoop():
import wx.lib.inspection
wx.lib.inspection.InspectionTool().Show()
That will let you easily see the actual size (and other info) for each widget in the application, like this:

Related

My wx.OnPaint() function is not called constantly in the wx main loop

I working on a project using OpenGL and wxPython to make a navigatable 3D user interface.
However, the OnPaint() function doesn't seem to be called continuously in the main loop. That makes my interface not being updated constantly. The function is only called I drag around the window. For example: when I press an arrow key, the object in the window only moves when I drag around the window.
I boiled my code down to these lines of codes. Can anyone help me make the OnPaint() function called constantly in print a bunch of "HI" without the need to drag around the window?
if __name__ == "__main__":
app = MyApp()
app.MainLoop()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame()
frame.Show()
return True
class MyFrame(wx.Frame):
def __init__(self):
self.size = (1000, 700)
wx.Frame.__init__(self, None, title ="wx", size=self.size)
self.canvas = MyCanvas(self)
class MyCanvas(GLCanvas):
def __init__(self, parent):
GLCanvas.__init__(self, parent, -1, size=(1000, 700))
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
print("HI")
A wx.Window like wx.Frame or GLCanvascan be forced to be repainted by .Refresh().
e.g. You can use the wx.IdleEvent to triggers the canvas to be refreshed continuously.
class MyFrame(wx.Frame):
def __init__(self):
self.size = (1000, 700)
wx.Frame.__init__(self, None, title ="wx", size=self.size)
self.canvas = MyCanvas(self)
self.Bind(wx.EVT_IDLE, self.OnIdle)
def OnIdle(self, event):
self.Refresh() # refresh self and all its children
#self.canvas.Refresh() # refresh self.canvas

wxpython - Erase background erases non-background components

In wxpython, I want to have a window with a picture that changes based on use of toolbar buttons with text controls on top of the picture. When I click the toolbar buttons, I am posting an erase background event, then capturing the erase event, and redrawing the new background from there (base on this).
Mostly works well, except that the text controls cease to be drawn once I redraw the background. They're still there, just not drawn.
Here is a simplified code that demonstrates the problem. If you run this code and click the button to toggle drawing the background image or not, the text controls disappear.:
import wx
import wx.lib.inspection
class PanelWithDrawing(wx.Panel):
def __init__(self, parent):
super(PanelWithDrawing, self).__init__(parent, size=(100, 40))
self.showbmp = False
self.txt = wx.TextCtrl(self, pos=(10, 10))
def onErase(self, dc):
if self.showbmp:
# dc.DrawBitmap(wx.Bitmap('background.png', 0, 0)
dc.DrawRectangle(0, 0, 40, 40) # use a drawing instead so you don't have to find a png
class Toolbar(wx.ToolBar):
def __init__(self, parent):
super(Toolbar, self).__init__(parent, -1)
self.AddLabelTool(wx.ID_SAVE, "Record", wx.Bitmap("picture.png", wx.BITMAP_TYPE_ANY), wx.NullBitmap, wx.ITEM_NORMAL, "", "")
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title)
self.toolbar = Toolbar(self)
self.SetToolBar(self.toolbar)
self.toolbar.Realize()
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self.panel1 = PanelWithDrawing(self.panel)
vbox.Add(self.panel1)
# self.panel2 = PanelWithText(self.panel)
# vbox.Add(self.panel2)
self.panel.SetSizer(vbox)
self.Centre()
self.Show()
self.toolbar.Bind(wx.EVT_TOOL, self.onButton)
self.panel1.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase)
def onErase(self, evt):
try:
dc = evt.GetDC()
except:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
self.panel1.onErase(dc)
def onButton(self, evt):
self.panel1.showbmp = not self.panel1.showbmp
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND))
if __name__ == '__main__':
app = wx.App()
Example(None, title='Example')
wx.lib.inspection.InspectionTool().Show() # use this for debugging GUI design
app.MainLoop()
How do I tell wxpython to draw all the non-background stuff again? Alternatively, how do I not un-draw it in the first place?
After working on it for a few days, I got it! And the answer is trivially simple (as usual).
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND)) should be replaced with self.Refresh() to refresh the whole frame and not just force a specific (and apparently unsafe) redraw.

How to make a canvas (rectangle) in wxpython?

I am trying to make a windows canvas type rectangle, here is an image if you are having issues understanding what i mean,
Is there any way todo this in wxpython? ( also, is there a way to set it to automatically adjust to the window width -20px?, so a radius around the window, and will adjust to the users window size.
EDIT: I asked on the wxPython IRC channel and a fellow named "r4z" came up with the following edit to my code which worked for me on Windows 7.
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.Bind(wx.EVT_PAINT, self.OnPaint)
#----------------------------------------------------------------------
def OnPaint(self, event):
""""""
pdc = wx.PaintDC(self)
try:
dc = wx.GCDC(pdc)
except:
dc = pdc
w, h = self.GetSizeTuple()
w = w - 10
h = h - 10
dc.Clear()
dc.DrawRectangle(x=5, y=5, width=w, height=h)
#----------------------------------------------------------------------
def OnSize(event):
event.EventObject.Refresh()
event.Skip()
if __name__ == "__main__":
app = wx.App(False)
frame = wx.Frame(None, title="Test")
panel = MyPanel(frame)
frame.Bind(wx.EVT_SIZE, OnSize)
frame.Show()
app.MainLoop()
Alternately, you might look at the wx.StaticBox widget.
EDIT #2: You could also just set the frame's style like this and skip the whole OnSize business:
frame = wx.Frame(None, title="Test", style=wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)

Changing Label in toolbar using wxPython

I currently have a toolbar in wxpython with an start icon. I want it so when this icon is clicked the icon and method that it uses changes to stop.
This is the code that I have so far:
#!/usr/bin/env python
# encoding: utf-8
"""
logClient2.py
Created by Allister on 2010-11-30.
"""
import wx
import sqlite3
WINDOW_SIZE = (900,400)
class logClient(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=WINDOW_SIZE)
self.toolbar = self.CreateToolBar()
self.toolbar.AddLabelTool(1, 'Refresh', wx.Bitmap('icons/refresh_icon.png'))
self.toolbar.Realize()
self.Bind(wx.EVT_TOOL, self.startLiveUpdate, id=1)
self.Show(True)
def startLiveUpdate(self, event):
pass
if __name__ == '__main__':
app = wx.App(False)
logClient(None, -1, "Log Event Viewer")
app.MainLoop()
Not really sure what to put in the startLiveUpdate method ?
Thanks for any help!
Here's a quickly hacked together one. Tested on Ubuntu 9.10, Python 2.6, wx 2.8.10.1
#!/usr/bin/env python
# encoding: utf-8
"""
logClient2.py
Created by Allister on 2010-11-30.
"""
import wx
import sqlite3
WINDOW_SIZE = (900,400)
class logClient(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=WINDOW_SIZE)
self.toolbar = self.CreateToolBar()
self.startLiveUpdate(None)
self.Show(True)
def startLiveUpdate(self, event):
self.createToolbarItem("Refresh", "refresh.jpg", self.stopLiveUpdate)
def stopLiveUpdate(self, event):
self.createToolbarItem("Stop", "refresh2.jpg", self.startLiveUpdate)
def createToolbarItem(self, label, imageName, method):
self.toolbar.RemoveTool(1)
self.toolbar.AddLabelTool(1, label, wx.Bitmap(imageName))
self.toolbar.Realize()
self.Bind(wx.EVT_TOOL, method, id=1)
if __name__ == '__main__':
app = wx.App(False)
logClient(None, -1, "Log Event Viewer")
app.MainLoop()
Here is a less hacked version than the accepted answer for wxpython 4.x
self.tool = self.toolbar.AddTool(-1, "A tool", "image.png")
...
def change_tool_label(self):
self.tool.SetLabel("A new label")
# need to call Realize() to re-draw the toolbar
self.toolbar.Realize()

wxpython drag&drop focus problem

I'd like to implement drag&drop in wxPython that works in similar way that in WordPad/Eclipse etc. I mean the following:
when something is being dropped to WordPad, WordPad window is on top with focus and text is added. In Eclipse editor text is pasted, Eclipse window gains focus and is on top.
When I implement drag&drop using wxPython target window is not brought to front. I implemented drag&drop in similar way to (drag):
import wx
class DragFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.tree = wx.TreeCtrl(self, wx.ID_ANY)
root = self.tree.AddRoot("root item")
self.tree.AppendItem(root, "child 1")
self.tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self.__onBeginDrag)
def __onBeginDrag(self, event):
tdo = wx.PyTextDataObject(self.tree.GetItemText(event.GetItem()))
dropSource = wx.DropSource(self.tree)
dropSource.SetData(tdo)
dropSource.DoDragDrop(True)
app = wx.PySimpleApp()
frame = DragFrame()
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
Second program (drop):
import wx
class TextDropTarget(wx.TextDropTarget):
def __init__(self, obj):
wx.TextDropTarget.__init__(self)
self.obj = obj
def OnDropText(self, x, y, data):
self.obj.WriteText(data + '\n\n')
wx.MessageBox("Error", "Error", style = wx.ICON_ERROR)
class DropFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
text = wx.TextCtrl(self, wx.ID_ANY)
text.SetDropTarget(TextDropTarget(text))
app = wx.PySimpleApp()
frame = DropFrame()
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
When you run both programs, place windows in the centre of the screen (part of drop window is visible), then drag a node from drag window to drop window - target window displays message box which isn't visible, target window is hidden behind source window.
How to implement drag&drop that will focus on the second (target) window? I've tried adding window.Show(), window.SetFocus(), even using some functions of WinAPI (through win32gui). I think there should be some standard way of doing this. What am I missing?
You need to do anything you want int DragOver method of DropTarget e.g. there you can raise and set focus on your window
sample working code for target
import wx
class TextDropTarget(wx.TextDropTarget):
def __init__(self, obj, callback):
wx.TextDropTarget.__init__(self)
self.obj = obj
self._callback = callback
def OnDropText(self, x, y, data):
self.obj.WriteText(data + '\n\n')
wx.MessageBox("Error", "Error", style = wx.ICON_ERROR)
def OnDragOver(self, *args):
wx.CallAfter(self._callback)
return wx.TextDropTarget.OnDragOver(self, *args)
class DropFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
text = wx.TextCtrl(self, wx.ID_ANY)
text.SetDropTarget(TextDropTarget(text, self._callback))
def _callback(self):
self.Raise()
self.SetFocus()
app = wx.PySimpleApp()
frame = DropFrame()
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
Wouldn't this work?
class DropFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
text = wx.TextCtrl(self, wx.ID_ANY)
self.SetFocus() # Set's the focus to this window, allowing it to receive keyboard input.
text.SetDropTarget(TextDropTarget(text))
wx.Frame inherits from wx.Window, that has SetFocus(self).
I have just tested it and it works. Just moved SetFocus before SetDropTarget, as its a cleaner behavior.

Categories