Bind double-click from everywhere on a wx.PyControl - python

I'm creating a clickable image (with text+bitmap) widget class as a subclass of wx.PyControl. I want to bind double-click anywhere on this widget (on text, on bitmap, on background) to some action (called OnClick here).
Unfortunately, it doesn't work : with this code, OnClick is not called, when I click on the bitmap or text part of the MyBitmapButton widget. (See question below).
import wx
class MyBitmapButton(wx.PyControl):
def __init__(self, parent, id=-1, bmp=None, label='blah', pos = wx.DefaultPosition, size=(166,220),
style = 0, validator = wx.DefaultValidator,
name = "mybitmapbutton"):
style |= wx.BORDER_NONE
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
myimg = wx.StaticBitmap(self, -1, bmp, pos=(8,8), size=(150,150))
mytxt = wx.StaticText(self, -1, label, (6,165))
class MainFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(800,600))
sizer = wx.WrapSizer()
img = wx.Image('background.png', wx.BITMAP_TYPE_PNG)
bmp = wx.BitmapFromImage(img)
btn = MyBitmapButton(self, -1, bmp, label='a')
sizer.Add(btn, 0, wx.ALL, 10)
self.Sizer = wx.BoxSizer(wx.VERTICAL)
self.Sizer.Add(sizer, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 8)
self.Show()
# ################
# Why don't these bindings detect double-click anywhere on the widget ?
btn.Bind(wx.EVT_LEFT_DCLICK, self.OnClick)
self.Bind(wx.EVT_LEFT_DCLICK, self.OnClick, btn)
def OnClick(self, event):
print 'Clicked'
app = wx.App(0)
frame = MainFrame(None, 'Test')
app.MainLoop()
How to bind double-click on anywhere on the button to OnClick ?

There is no magical solution, you need to call Bind() on myimg and mytxt as well. Of course, you can do it inside MyBitmapButton to encapsulate this inside your custom control, i.e. bind to the double clicks on its sub-controls and forward them to the button itself -- then your existing btn.Bind() would be enough.
The only (ugly) alternative is to handle events at wx.App level as it gets all of them and, in principle, you could check if the double click comes from a main frame child and then handle it. But this is bad practice and I don't recommend doing it.

Related

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.

Pycharm doesn't run the script properly, instead it runs "unittests" in the script

I use Pycharm Community Edition 3.4.1, I want to run wxpython's demo file "Dialog.py".
When I run this script with IDLE, it runs fine.
But when I try to run it with Pycharm, it won't run as it does in IDLE, instead it tries to run "unittests" in the script (I don't know what unittest is):
The script is:
import wx
#---------------------------------------------------------------------------
# Create and set a help provider. Normally you would do this in
# the app's OnInit as it must be done before any SetHelpText calls.
provider = wx.SimpleHelpProvider()
wx.HelpProvider.Set(provider)
#---------------------------------------------------------------------------
class TestDialog(wx.Dialog):
def __init__(
self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE,
useMetal=False,
):
# Instead of calling wx.Dialog.__init__ we precreate the dialog
# so we can set an extra style that must be set before
# creation, and then we create the GUI object using the Create
# method.
pre = wx.PreDialog()
pre.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP)
pre.Create(parent, ID, title, pos, size, style)
# This next step is the most important, it turns this Python
# object into the real wrapper of the dialog (instead of pre)
# as far as the wxPython extension is concerned.
self.PostCreate(pre)
# This extra style can be set after the UI object has been created.
if 'wxMac' in wx.PlatformInfo and useMetal:
self.SetExtraStyle(wx.DIALOG_EX_METAL)
# Now continue with the normal construction of the dialog
# contents
sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(self, -1, "This is a wx.Dialog")
label.SetHelpText("This is the help text for the label")
sizer.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
box = wx.BoxSizer(wx.HORIZONTAL)
label = wx.StaticText(self, -1, "Field #1:")
label.SetHelpText("This is the help text for the label")
box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
text = wx.TextCtrl(self, -1, "", size=(80,-1))
text.SetHelpText("Here's some help text for field #1")
box.Add(text, 1, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
box = wx.BoxSizer(wx.HORIZONTAL)
label = wx.StaticText(self, -1, "Field #2:")
label.SetHelpText("This is the help text for the label")
box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
text = wx.TextCtrl(self, -1, "", size=(80,-1))
text.SetHelpText("Here's some help text for field #2")
box.Add(text, 1, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL)
sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5)
btnsizer = wx.StdDialogButtonSizer()
if wx.Platform != "__WXMSW__":
btn = wx.ContextHelpButton(self)
btnsizer.AddButton(btn)
btn = wx.Button(self, wx.ID_OK)
btn.SetHelpText("The OK button completes the dialog")
btn.SetDefault()
btnsizer.AddButton(btn)
btn = wx.Button(self, wx.ID_CANCEL)
btn.SetHelpText("The Cancel button cancels the dialog. (Cool, huh?)")
btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
#---------------------------------------------------------------------------
class TestPanel(wx.Panel):
def __init__(self, parent, log):
self.log = log
wx.Panel.__init__(self, parent, -1)
b = wx.Button(self, -1, "Create and Show a custom Dialog", (50,50))
self.Bind(wx.EVT_BUTTON, self.OnButton, b)
if 'wxMac' in wx.PlatformInfo:
self.cb = wx.CheckBox(self, -1, "Set Metal appearance", (50,90))
def OnButton(self, evt):
useMetal = False
if 'wxMac' in wx.PlatformInfo:
useMetal = self.cb.IsChecked()
dlg = TestDialog(self, -1, "Sample Dialog", size=(350, 200),
#style=wx.CAPTION | wx.SYSTEM_MENU | wx.THICK_FRAME,
style=wx.DEFAULT_DIALOG_STYLE, # & ~wx.CLOSE_BOX,
useMetal=useMetal,
)
dlg.CenterOnScreen()
# this does not return until the dialog is closed.
val = dlg.ShowModal()
if val == wx.ID_OK:
self.log.WriteText("You pressed OK\n")
else:
self.log.WriteText("You pressed Cancel\n")
dlg.Destroy()
#---------------------------------------------------------------------------
def runTest(frame, nb, log):
win = TestPanel(nb, log)
return win
#---------------------------------------------------------------------------
overview = """\
wxPython offers quite a few general purpose dialogs for useful data input from
the user; they are all based on the wx.Dialog class, which you can also subclass
to create custom dialogs to suit your needs.
The Dialog class, in addition to dialog-like behaviors, also supports the full
wxWindows layout featureset, which means that you can incorporate sizers or
layout constraints as needed to achieve the look and feel desired. It even supports
context-sensitive help, which is illustrated in this example.
The example is very simple; in real world situations, a dialog that had input
fields such as this would no doubt be required to deliver those values back to
the calling function. The Dialog class supports data retrieval in this manner.
<b>However, the data must be retrieved prior to the dialog being destroyed.</b>
The example shown here is <i>modal</i>; non-modal dialogs are possible as well.
See the documentation for the <code>Dialog</code> class for more details.
"""
if __name__ == '__main__':
import sys,os
import run
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
How can I run this file with Pycharm as I do with IDLE?
In PyCharm right-click the area where you wrote:
if __name__ == '__main__':
This will show you the normal "Run Dialog" option. Save this config for further use.
Have you configured a Pycharm Run Configuration?
Go to Run menu, pick Edit Configuration and make sure to configure one by picking which Python interpreter to use.

How can I add and remove controls in the page of a Notebook in wxPython?

Based on user interaction, I would like to dynamically add and remove controls to a panel in a wxPython notebook. The approach I've tried most thoroughly is to call .Clear() on the panel's sizer and add all new controls. However, on both Windows 7 and Linux desktops, rendering artifacts and stale controls remain visible under the new contents. How can I completely remove the old controls and add new controls without these artifacts?
Below is a sample program that reproduces the issue on Windows 7. Note the two different .update() methods of StaticPanel and DynamicPanel:
#!/usr/bin/python
import wx
import sys
class StaticPane(wx.Panel):
"""A panel that contains simple text that is updated
when the .update() method is called. The text is updated
using .SetText(), and the text control sticks around
between calls to .update()."""
def __init__(self, *args, **kwargs):
super(StaticPane, self).__init__(*args, **kwargs)
self._sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self._sizer)
self._counter = 0
self._base_text = "Some Text"
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
def update(self):
self._counter += 1
self._text.SetValue(self._base_text + "!" * self._counter)
class DynamicPane(wx.Panel):
"""A panel that contains simple text that is updated
when the .update() method is called. The text is updated
by removing the existing text control, and adding a new one
with the updated text string."""
def __init__(self, *args, **kwargs):
super(DynamicPane, self).__init__(*args, **kwargs)
self._sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self._sizer)
self._counter = 0
self._base_text = "Some Text"
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
def update(self):
self._counter += 1
self._sizer.Clear()
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
self.Layout()
class TestViewer(wx.Frame):
"""A Frame with a button and a notebook. When the button is pressed,
each of the two pages in the notebook recieve a call to .update().
"""
def __init__(self, parent):
super(TestViewer, self).__init__(parent, -1, "Test Viewer")
self.Bind(wx.EVT_CLOSE, self.OnClose)
self._panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self._panel.SetSizer(vbox)
update_button = wx.Button(self._panel, wx.ID_CLOSE, "Update")
update_button.Bind(wx.EVT_BUTTON, self.update)
vbox.Add(update_button, 0, wx.EXPAND)
self._nb = wx.Notebook(self._panel)
self._view_one = StaticPane(self._nb, -1)
self._view_two = DynamicPane(self._nb, -1)
self._nb.AddPage(self._view_one, "One")
self._nb.AddPage(self._view_two, "Two")
vbox.Add(self._nb, 1, wx.EXPAND | wx.ALL)
self.Layout()
def update(self, e):
self._view_one.update()
self._view_two.update()
def OnClose(self, event):
sys.exit(0)
if __name__ == "__main__":
app = wx.App(False)
frame = TestViewer(None)
frame.Show()
app.MainLoop()
Click the button "Update" to update the text in the two panels. The first panel updates the text control by using .SetText(), while the second panel replaces the TextCtrl with a new one. Note that as you resize the window or mouseover the second panel after a few button clicks, there are overlapping controls and other artifacts.
Here are screenshots showing the stacked controls. Both images were taken after the same number of button clicks, they just show the two different panels at the same state. I expected the text to be exactly the same.
Simply clearing the sizer will only remove the references to its contents, not the widgets. When you create a widget, it will register with the parent window you supplied. Consider the following code:
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.textCtrl = wx.TextCtrl(self) # create with Frame as parent
self.textCtrl = None # has no effect on the TextCtrl
The parent window (Frame in this example) will take ownership of the TextCtrl and even if you set it to None, the Frame will keep it alive until it's is destroyed. To remove the TextCtrl, you have to destroy it explicitly:
self.textCtrl.Destroy()
If you want to remove all child widgets at once, you can use:
self.DestroyChildren()

WxPython: Multiple Widgets in a Child Window

I'm teaching myself (Wx) Python and got stuck. I want to create a child window that has a recurring set of information inside of my TestFrame, which contains most of my code. The problem is, it only shows one widget in my code. I am trying to figure out the following, in order of importance to me.
**Note that this code is an extension to this page.*
How can I allow multiple widgets in the "AddBox" class to appear correctly?
When my window is resized, it corrupts the button images as in the screen attached. How could I fix this?
How do you call/bind each of these dynamically created widgets?
Bonus: is the "OnSize" module needed here?
Thank you for your help. If allowed/appropriate, I'm willing to contribute $5 via Paypal to the winner if you PM me.
import wx
class AddBox(wx.Window):
def __init__(self, parent):
wx.Window.__init__(self, parent)
pbox = wx.BoxSizer(wx.VERTICAL)
controlback = wx.Button(self, label="Back")
controlforward = wx.Button(self, label="Forward")
pbox.AddMany([(controlback, 1, wx.ALL), (controlforward, 1, wx.ALL)])
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, size=(1000, 550))
self.Bind(wx.EVT_SIZE, self.OnSize)
pbox0 = wx.BoxSizer(wx.VERTICAL)
controlback0 = wx.Button(self, label="Back0")
controlforward0 = wx.Button(self, label="Forward0")
pbox0.AddMany([(controlback0, 1, wx.ALL), (controlforward0, 1, wx.ALL)])
pbox2 = wx.BoxSizer(wx.VERTICAL)
self.scrolling_window = wx.ScrolledWindow( self )
self.scrolling_window.SetScrollRate(1,6)
self.scrolling_window.EnableScrolling(True,True)
self.sizer_container = wx.BoxSizer( wx.VERTICAL )
self.sizer = wx.BoxSizer( wx.VERTICAL )
self.sizer_container.Add(self.sizer,1,wx.CENTER,wx.EXPAND)
self.child_windows = []
for i in range(0,8):
wind = AddBox(self.scrolling_window)
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
self.child_windows.append(wind)
self.scrolling_window.SetSizer(self.sizer_container)
#self.Layout() #not needed?
pbox2.AddMany([(self.sizer_container, 1, wx.ALL)])
def OnSize(self, event):
self.scrolling_window.SetSize(self.GetClientSize())
if __name__=='__main__':
app = wx.PySimpleApp()
f = TestFrame()
f.Show()
app.MainLoop()
The code here is pretty convoluted and hard to follow. You almost never need to use wx.Window. In fact, in almost 6 years of using wxPython, I have NEVER used it directly. PySimpleApp is deprecated as well. Anyway, I cleaned up the code a bunch and re-did it below. I'm not sure if this is what you're looking for or not though. Also note that I swapped out ScrolledWindow for ScrolledPanel as I think the latter is easier to use:
import wx
import wx.lib.scrolledpanel as scrolled
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=(1000, 550))
panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
pbox0 = wx.BoxSizer(wx.VERTICAL)
controlback0 = wx.Button(panel, label="Back0")
controlforward0 = wx.Button(panel, label="Forward0")
pbox0.Add(controlback0, 0, wx.ALL)
pbox0.Add(controlforward0, 0, wx.ALL)
mainSizer.Add(pbox0)
self.scrolling_window = scrolled.ScrolledPanel( panel )
self.scrolling_window.SetAutoLayout(1)
self.scrolling_window.SetupScrolling()
self.sizer = wx.BoxSizer( wx.VERTICAL )
self.child_windows = []
for i in range(0,8):
wind = self.addBox()
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
self.scrolling_window.SetSizer(self.sizer)
mainSizer.Add(self.scrolling_window, 1, wx.EXPAND)
panel.SetSizer(mainSizer)
def addBox(self):
pbox = wx.BoxSizer(wx.VERTICAL)
controlback = wx.Button(self.scrolling_window, label="Back")
controlforward = wx.Button(self.scrolling_window, label="Forward")
pbox.AddMany([(controlback, 0, wx.ALL), (controlforward, 0, wx.ALL)])
return pbox
def OnSize(self, event):
self.scrolling_window.SetSize(self.GetClientSize())
if __name__=='__main__':
app = wx.App(False)
f = TestFrame()
f.Show()
app.MainLoop()
You should also take a look at the Widget Inspection Tool. It will help you figure out layout problems like this: http://wiki.wxpython.org/Widget%20Inspection%20Tool
EDIT: I think the code above takes care of items #1 and 2. For #3, see the following article I wrote last year: http://www.blog.pythonlibrary.org/2011/09/20/wxpython-binding-multiple-widgets-to-the-same-handler/
Basically you bind in the loop itself and then in the event handler, you can use event.GetEventObject() to return the calling widget and set its value or whatever. As for your 4th question, I would say no, you don't need the OnSize method. You almost never need to override that in wxPython and I certainly didn't in my example code.
I just have an answer to 3 for now: Assign to each dynamically created widget an ID, store this ID in a dictionary ({id : widget }) and pass as the callback function a lambda-function or use functools.partial to pass the ID to the "real" callback. (of course you can pass the widget directly over the lambda-function, but I like to have instances to the created widgets somewhere, if I need to access these sometime)
def __init__(...):
self.event_dispatch = dict()
...
for id in xrange(500, 508):
wind = AddBox(self.scrolling_window, id)
...
self.event_dispatch[id] = wind
self.scrolling_window.Bind(wx.MY_EVENT, lambda evt: self.on_event(evt, id), id=id)
def on_event(event, id):
widget = self.event_dispatch[id]
# do something with widget

wxPython problems with wrapping staticText

A simplified version of the code is posted below (white space, comments, etc. removed to reduce size - but the general format to my program is kept roughly the same).
When I run the script, the static text correctly wraps as it should, but the other items in the panel do not move down (they act as if the statictext is only one line and thus not everything is visible).
If I manually resize the window/frame, even just a tiny amount, everything gets corrected, and displays as it is should.
Why doesn't it display correctly to begin with? I've tried all sorts of combination's of GetParent().Refresh() or Update() and GetTopLevelParent().Update() or Refresh(). I've also tried everything I can think of but cannot get it to display correctly without manually resizing the frame/window. Once re-sized, it works exactly as I want it to.
Information:
Windows XP
Python 2.5.2
wxPython 2.8.11.0 (msw-unicode)
My Code:
#! /usr/bin/python
import wx
class StaticWrapText(wx.PyControl):
def __init__(self, parent, id=wx.ID_ANY, label='', pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER,
validator=wx.DefaultValidator, name='StaticWrapText'):
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
self.statictext = wx.StaticText(self, wx.ID_ANY, label, style=style)
self.wraplabel = label
#self.wrap()
def wrap(self):
self.Freeze()
self.statictext.SetLabel(self.wraplabel)
self.statictext.Wrap(self.GetSize().width)
self.Thaw()
def DoGetBestSize(self):
self.wrap()
#print self.statictext.GetSize()
self.SetSize(self.statictext.GetSize())
return self.GetSize()
class TestPanel(wx.Panel):
def __init__(self, *args, **kwargs):
# Init the base class
wx.Panel.__init__(self, *args, **kwargs)
self.createControls()
def createControls(self):
# --- Panel2 -------------------------------------------------------------
self.Panel2 = wx.Panel(self, -1)
msg1 = 'Below is a List of Files to be Processed'
staticBox = wx.StaticBox(self.Panel2, label=msg1)
Panel2_box1_v1 = wx.StaticBoxSizer(staticBox, wx.VERTICAL)
Panel2_box2_h1 = wx.BoxSizer(wx.HORIZONTAL)
Panel2_box3_v1 = wx.BoxSizer(wx.VERTICAL)
self.wxL_Inputs = wx.ListBox(self.Panel2, wx.ID_ANY, style=wx.LB_EXTENDED)
sz = dict(size=(120,-1))
wxB_AddFile = wx.Button(self.Panel2, label='Add File', **sz)
wxB_DeleteFile = wx.Button(self.Panel2, label='Delete Selected', **sz)
wxB_ClearFiles = wx.Button(self.Panel2, label='Clear All', **sz)
Panel2_box3_v1.Add(wxB_AddFile, 0, wx.TOP, 0)
Panel2_box3_v1.Add(wxB_DeleteFile, 0, wx.TOP, 0)
Panel2_box3_v1.Add(wxB_ClearFiles, 0, wx.TOP, 0)
Panel2_box2_h1.Add(self.wxL_Inputs, 1, wx.ALL|wx.EXPAND, 2)
Panel2_box2_h1.Add(Panel2_box3_v1, 0, wx.ALL|wx.EXPAND, 2)
msg = 'This is a long line of text used to test the autowrapping '
msg += 'static text message. '
msg += 'This is a long line of text used to test the autowrapping '
msg += 'static text message. '
msg += 'This is a long line of text used to test the autowrapping '
msg += 'static text message. '
msg += 'This is a long line of text used to test the autowrapping '
msg += 'static text message. '
staticMsg = StaticWrapText(self.Panel2, label=msg)
Panel2_box1_v1.Add(staticMsg, 0, wx.ALL|wx.EXPAND, 2)
Panel2_box1_v1.Add(Panel2_box2_h1, 1, wx.ALL|wx.EXPAND, 0)
self.Panel2.SetSizer(Panel2_box1_v1)
# --- Combine Everything -------------------------------------------------
final_vbox = wx.BoxSizer(wx.VERTICAL)
final_vbox.Add(self.Panel2, 1, wx.ALL|wx.EXPAND, 2)
self.SetSizerAndFit(final_vbox)
class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
# Init the base class
wx.Frame.__init__(self, *args, **kwargs)
panel = TestPanel(self)
self.SetClientSize(wx.Size(500,500))
self.Center()
class wxFileCleanupApp(wx.App):
def __init__(self, *args, **kwargs):
# Init the base class
wx.App.__init__(self, *args, **kwargs)
def OnInit(self):
# Create the frame, center it, and show it
frame = TestFrame(None, title='Test Frame')
frame.Show()
return True
if __name__ == '__main__':
app = wxFileCleanupApp()
app.MainLoop()
Using Mike Driscoll's code as a baseline, I hope this demonstrates my issue. There are two different versions of using "txt". Here are three things I want you to try:
Run it as-is. With my StaticWrapText. It displays wrong at first, but re-size the window and it works EXACTLY as I want. There is no blank/wasted space below the text before the "button"
Change these two lines (change the comments):
txt = wx.StaticText(panel, label=text)
#txt = StaticWrapText(panel, label=text)
Now you will see there is no wrapping and the text is always on only one line. Definitely not what we want. This is because of "sizer.Add(txt, 0, wx.EXPAND, 5) "...so going on to Part 3...
Keep the change from Part 2 and also change:
sizer.Add(txt, 0, wx.EXPAND, 5)
to:
sizer.Add(txt, 1, wx.EXPAND, 5)
So now the statictext will expand. This is CLOSE to working...BUT I don't want all that wasted space between the text and the button. If you make the window large, there is a lot of wasted space. See Part 1 after the window is re-sized to see the difference.
Code:
import wx
class StaticWrapText(wx.PyControl):
def __init__(self, parent, id=wx.ID_ANY, label='', pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER,
validator=wx.DefaultValidator, name='StaticWrapText'):
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
self.statictext = wx.StaticText(self, wx.ID_ANY, label, style=style)
self.wraplabel = label
#self.wrap()
def wrap(self):
self.Freeze()
self.statictext.SetLabel(self.wraplabel)
self.statictext.Wrap(self.GetSize().width)
self.Thaw()
def DoGetBestSize(self):
self.wrap()
#print self.statictext.GetSize()
self.SetSize(self.statictext.GetSize())
return self.GetSize()
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
text = "I'm subclasses the statictext because I want it to act exactly like a static text, but correctly wordwrap as needed. I've found several examples of it on the web, but none that worked how I wanted. The wordwrap makes it look much nicer when the user may decide to re-size the window, so I would definitely like to have it be wordwrapped. I know about the wx.lib.wordwrap, but chose to use the built in Wrap function of the statictext control instead. It basically does the same thing from what I understand."
#txt = wx.StaticText(panel, label=text)
txt = StaticWrapText(panel, label=text)
wxbutton = wx.Button(panel, label='Button', size=wx.Size(120,50))
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.EXPAND, 5)
sizer.Add(wxbutton, 1, wx.EXPAND, 5)
panel.SetSizer(sizer)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
EDIT:
AHHH...finally! I tried using the Layout() method on virtually every level of the program, but I actually needed to use Layout() on the SIZER which is found with the method GetSizer() - or you can send SendSizeEvent() to the panel (commented in code below). Thus, the following now does EXACTLY what I want! Thanks for the help. The only other change was to store the panel with self.panel in the frame class. As a note, I had to put this statement AFTER the frame.Show() or it didn't work correctly.
Code:
import wx
class StaticWrapText(wx.PyControl):
def __init__(self, parent, id=wx.ID_ANY, label='', pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER,
validator=wx.DefaultValidator, name='StaticWrapText'):
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
self.statictext = wx.StaticText(self, wx.ID_ANY, label, style=style)
self.wraplabel = label
#self.wrap()
def wrap(self):
self.Freeze()
self.statictext.SetLabel(self.wraplabel)
self.statictext.Wrap(self.GetSize().width)
self.Thaw()
def DoGetBestSize(self):
self.wrap()
#print self.statictext.GetSize()
self.SetSize(self.statictext.GetSize())
return self.GetSize()
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
text = "I'm subclasses the statictext because I want it to act exactly like a static text, but correctly wordwrap as needed. I've found several examples of it on the web, but none that worked how I wanted. The wordwrap makes it look much nicer when the user may decide to re-size the window, so I would definitely like to have it be wordwrapped. I know about the wx.lib.wordwrap, but chose to use the built in Wrap function of the statictext control instead. It basically does the same thing from what I understand."
txt = StaticWrapText(self.panel, label=text)
wxbutton = wx.Button(self.panel, label='Button', size=wx.Size(120,50))
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.EXPAND, 5)
sizer.Add(wxbutton, 1, wx.EXPAND, 5)
self.panel.SetSizer(sizer)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm()
frame.Show()
#frame.panel.SendSizeEvent()
frame.panel.GetSizer().Layout()
app.MainLoop()
As a final note, in my original program posted, the following line needs to be added just before or after frame.Show():
frame.panel.Panel2.GetSizer().Layout()
Interestingly...with that original example this can be before or after frame.Show() but the other example requires that it be after frame.Show(). I'm not sure why, but just put it after and you're safe.
I use
width = 200 # panel width
txt = wx.StaticText(panel, label=text)
txt.Wrap(width)
This works great and the next widgets are positioned correctly. You can easily do the txt.Wrap(width) dynamically.
Why are you subclassing it? Do you need wordwrap? If so, there's a module for that in wx.lib.wordwrap that you can use.
In answer the the OP's comment, check this out:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
text = "I'm subclasses the statictext because I want it to act exactly like a static text, but correctly wordwrap as needed. I've found several examples of it on the web, but none that worked how I wanted. The wordwrap makes it look much nicer when the user may decide to re-size the window, so I would definitely like to have it be wordwrapped. I know about the wx.lib.wordwrap, but chose to use the built in Wrap function of the statictext control instead. It basically does the same thing from what I understand."
txt = wx.StaticText(panel, label=text)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(txt, 1, wx.EXPAND, 5)
panel.SetSizer(sizer)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
I used the OP's comment for the text. Anyway, this works fine for me on Windows XP, Python 2.5 and wxPython 2.8.10.1.
I found what I think is a much easier and automatic way to handle this issue.
After creating the StaticText control, bind the control's wx.EVT_SIZE to a handler that calls the StaticText's Wrap() function with the event's GetSize()[0] as an argument (and then skips the event).
An example:
class MyDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent = parent, title = "Test Dialog", style = wx.CAPTION)
bigstr = "This is a really long string that is intended to test the wrapping functionality of the StaticText control in this dialog. If it works correctly, it should appear as multiple lines of text with a minimum of fuss."
self.__label__ = wx.StaticText(parent = self, label = bigstr)
self.__actionbutton__ = wx.Button(parent = self, label = "Go")
self.__label__.Bind(wx.EVT_SIZE, self.__WrapText__)
self.__actionbutton__.Bind(wx.EVT_BUTTON, self.__OnButton__)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.__label__, flag = wx.ALL | wx.EXPAND, border = 5)
sizer.Add(self.__actionbutton__, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.CENTER, border = 0)
self.SetSizer(sizer)
self.Layout()
def __OnButton__(self, event):
self.EndModal(wx.ID_OK)
def __WrapText__(self, event):
self.__label__.Wrap(event.GetSize()[0])
event.Skip()
This is what it looks like on my system (MSW, Python 2.7.5, wx 2.8.12.1):

Categories