Introduction

Sometimes when you are implementing a dynamic layout of your widgets you will find yourself in the situation where the layout will not automatically adjust to the changes made in the widgets. For example you may have a static text widget where you change the label such that it is much larger than before, and that it will overlap other widgets if something is not done. Another example might involve adding new widgets to or removing widgets from the current layout.

Often simply calling the parent window's Layout method, or the top-level parent's Layout will adjust the layout of the widgets involved and all will be fine. But in the more complicated layouts that may not work. Another symptom of this problem may be that the layout is jumbled or otherwise incorrect, but "snaps into place" as soon as the top-level parent is resized a bit by the user. Before jumping into a discussion of the solution it would be helpful to better understand how auto-layout works.

Auto-Layout

We will only address layout implemented by sizers in this document, but you should keep in mind that there are some other ways to do it. Before sizers we had wx.LayoutConstraints for auto-layout, which some people still use. It's also possible to implement some sort of dynamic layout yourself in a window's EVT_SIZE handler by calculating positions and calling the children's SetPosition and/or SetSize methods as needed.

The auto-layout of the children of a window is typically triggered in the window's default EVT_SIZE event handler. That is why sometimes the layout can be "fixed" by resizing the the top-level window, or calling SendSizeEvent. After the size has changed then it is possible that the layout of the children will need to be adjusted to adapt to either the greater or the lesser space that is now available to them, so the EVT_SIZE handler will call the window's Layout method. If the window has a sizer then that will turn into a call to the sizer's Layout method. There the position and size of the child widgets will be calculated according to the primary algorithm of the sizer class (box, grid, etc.) and then the positions and sizes will be changed accordingly. If a child is resized then it will also get its own EVT_SIZE event and if it has its own sizer then it will go through its own auto-layout sequence for managing the layout of its own children. However, if one of the children does not need to be resized when the parent is recalculating its layout, then that child will not get an EVT_SIZE and will not have its sizer's Layout called, and this is usually the root of the problem that people experience garbled layouts after making adjustments or additions/deletions of child widgets.

The Problem

wxWidgets has a built-in optimization that essentially short-circuits the propagation of EVT_SIZE events when a parent window is resized. The parent will get the EVT_SIZE event, but it will not automatically also filter down to all the child windows that may want to know about the change in size. Those child windows only get their own EVT_SIZE event if something is done during the parent's EVT_SIZE handler that causes the child to actually change size. This actually a common-sense default behavior but I call it an optimization because most of the time it is actually not necessary for the size events to be sent to children and so most of the time it would be wasted time and effort to do so.

In the other few percent of the cases the problem happens when some change in a widget, for example changing the label of a static text, does not necessitate and change in total size of the immediate parent of the widget due to the nature of how its sizer is configured and what other elements are in the sizer. In that case calling Layout on the top-level parent will not result in the immediate parent's Layout being called and so the static text and its siblings are not adjusted at all. You can probably see that the solution in this case is to simply call the immediate parent's Layout method yourself when you make the change to the label. However, there is a still smaller percentage of cases where that is also not enough, where the position in the containment hierarchy that does not need to change size (from its parent's perspective) but where the layout of its content should be adjusted never the less.

The Solution

The solution is simple to state, but sometimes difficult to implement. It is simply to find the position in the containment hierarchy where a call to Layout is necessary, and then do that at the point where the change is made. (Note: this is actually a bad idea because it could increase the coupling between parts of your program that shouldn't need to know details about each other. See "A Better Idea" below.)

As stated above, figuring out the right parent, grandparent, great-grandparent that should have its Layout method called can be tricky. Using the Widget Inspection Tool can greatly help in this process. With the WIT you can get your program to the point where the layout is not adjusted properly and then select the parent and call its Layout from the WIT's PyShell and see if the layout is adjusted properly. If so then you've found your answer. If not then you can continue experimenting with the live widgets and see if you can figure out what would be a good solution.

If all else fails then you can go with a brute-force solution and simply call Layout on every parent from the changed widget up to the top-level parent. Something like this:

   1      widget = self.widgetThatWasChanged
   2      while widget.GetParent():
   3          widget = widget.GetParent()
   4          widget.Layout()
   5          if widget.IsTopLevel():
   6              break

A Better Idea

From a practical viewpoint poking into the widgets further up the containment hierarchy as shown above gets the job done, but from a conceptual viewpoint it is not a very good idea because it means that the widget where the change happens must know the details about the hierarchy and the structure and nature of the hierarchy, in other words, it needs to know stuff it shouldn't care about at all. It increases coupling between components of the UI and reduces the possibility for reuse of the component in other parts of your application that may have a different containment hierarchy. For example, suppose using the above techniques you find that your layout will be fixed if you do something like this:

   1     self.GetParent().GetParent().GetParent().Layout()

What happens if you later want to use this same widget class in a place that is not so deep in the containment hierarchy? You risk getting an error that can be annoying to deal with. Of course it is simple to deal with, but the point is that you shouldn't need to. Increasing coupling and reducing reuse are the exact opposite of some of the basic principles of Object Oriented Programming and our goal should be to move things in the other direction.

One way that I've dealt with this in some projects is to use a custom event whose purpose is to notify all the widgets in the containment hierarchy from the point of the change (changing the value of a label for example) up to the top-level window that something has happened that may require that a Layout should be done. For those parents up the hierarchy that may have the complex layout problem described on this page can simply implement a handler for that event and then call their own Layout when they see the event. Be sure to also call event.Skip() in that handler in case there are any other parents up the ladder that also need to Layout themselves. You can see an example of using an event like this in the wx.lib.resizewidget module in the wxPython library. Look at where EVT_RW_LAYOUT_NEEDED is created, and where it is used in _sendEvent.

--RobinDunn

Comments

WhenAndHowToCallLayout (last edited 2011-07-11 22:52:13 by c-98-246-90-205)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.