wxPython problems with wrapping staticText

10,064

Solution 1

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:

  1. 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"

  2. 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...

  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.

Solution 2

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.

Solution 3

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.

Solution 4

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): StaticText Wrapping Dialog

Share:
10,064
Scott B
Author by

Scott B

Updated on June 04, 2022

Comments

  • Scott B
    Scott B almost 2 years

    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()
    
  • Scott B
    Scott B over 13 years
    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.
  • Mike Driscoll
    Mike Driscoll over 13 years
    You might want to use SetSizeHints to keep the frame from being resized too small as well.
  • Scott B
    Scott B over 13 years
    OK, your example is smaller, so it will be easier to work with. See my answer for a modification to your code that demonstrates my issue (more clearly, I hope).
  • Mike Driscoll
    Mike Driscoll over 13 years
    I should have thought of that. You usually want to call Layout either on the parent of the widgets or the sizer that contains the widgets. Oh well. Sorry I didn't spot that.
  • Scott B
    Scott B over 13 years
    Thanks for the help! It still doesn't quite work as it should when I maximize/minimize the window. But for the moment, it is good enough.
  • Scott B
    Scott B over 13 years
    Ah, this makes it work even when the maximize button is used...in the wrap function, use self.statictext.Wrap(self.GetParent().GetSize().width) instead of self.statictext.Wrap(self.GetSize().width)
  • Pod
    Pod over 7 years
    Calling Wrap inside an EVT_SIZE handler seems to call the same handler again, this time with a different size.