Enable text wrapping inside a vertical sizer for wxStaticText (PR #25753)

35 views
Skip to first unread message

RobertRoeb

unread,
Sep 2, 2025, 10:58:12 AM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed

Changed the wxWrapSizer code from using a variable "m_usedLast" to two different functions
CalcMinFirstPass() and CalcMin() for the two step min size calculation.
Added the same logic and naming to controls using GetEffectiveMinSizeFirstPass()
Implemented this for the generic code in wxStaticTextBase.
Added test to wrapsizer sample.


You can view, comment on, or merge this pull request online at:

  https://github.com/wxWidgets/wxWidgets/pull/25753

Commit Summary

  • 45906cc Enable text wrapping inside a vertical size for wxStaticText similar to wxWrapSizer

File Changes

(9 files)

Patch Links:


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753@github.com>

RobertRoeb

unread,
Sep 2, 2025, 11:00:58 AM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed
RobertRoeb left a comment (wxWidgets/wxWidgets#25753)

So far only tested on GTK+. Will test on OSX later the week. I would appreciate testing on MSW.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/c3245720953@github.com>

VZ

unread,
Sep 2, 2025, 11:03:40 AM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed
vadz left a comment (wxWidgets/wxWidgets#25753)

Before looking at it, could you please explain the relationship between this and wxWrapSizer? I mean, they do conceptually similar things, but other than that, there is none, right?

Also, I don't know if we want to do this unconditionally or if some flag is specified. While people probably don't want their labels being truncated, they may not want them to take more than one line too... And the interaction of this with ellipsize flags is not clear at all.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/c3245731821@github.com>

VZ

unread,
Sep 2, 2025, 11:14:10 AM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed

@vadz commented on this pull request.

Sorry, there are a number of things to fix here, but more importantly I see 2 changes here that should ideally correspond to 2 commits:

  1. Addition of GetEffectiveMinSizeFirstPass() and changing wxWrapSizer to use it.
  2. Changes to wxStaticText.

(1) is probably fine, but it really needs to be documented.

For (2) I'm still not sure if we want to do it unconditionally or depending on a flag. If nothing else, this almost certainly shouldn't override explicit calls to Wrap() in the user code as it, I believe, does now.


In include/wx/sizer.h:

> +    // Can be overidden for two step process, e.g. by wxWrapSizer
+    virtual wxSize CalcMinFirstPass() { return CalcMin(); }

It would be nice to explain what the two step process is, because this is really not obvious.

Also, wxWrapSizer doesn't really need it, as it worked before adding it, so this makes things even more confusing.


In include/wx/window.h:

> @@ -411,6 +411,7 @@ class WXDLLIMPEXP_CORE wxWindowBase : public wxEvtHandler
         // minimum size, giving priority to the min size components, and
         // returns the results.
     virtual wxSize GetEffectiveMinSize() const;
+    virtual wxSize GetEffectiveMinSizeFirstPass() const { return  GetEffectiveMinSize(); }

This should really be documented (in a comment here and in interface/wx/window.h) because I just have no idea how to use it right now.


In include/wx/stattext.h:

> @@ -72,6 +77,12 @@ class WXDLLIMPEXP_CORE wxStaticTextBase : public wxControl
     // display.
     void UpdateLabel();
 
+    // save unwrapped label to allow to call Wrap() several times and 
+    // always starting from the original, unwrapped label
+    wxString m_unwrappedLabel;

Is this different from wxControl::m_labelOrig?


In include/wx/stattext.h:

> @@ -72,6 +77,12 @@ class WXDLLIMPEXP_CORE wxStaticTextBase : public wxControl
     // display.
     void UpdateLabel();
 
+    // save unwrapped label to allow to call Wrap() several times and 
+    // always starting from the original, unwrapped label
+    wxString m_unwrappedLabel;
+    int m_currentWrap = 0;

What is the value of this? Wrap width? With 0 meaning "none"? Please comment it too.


In samples/wrapsizer/wrapsizer.cpp:

> @@ -118,10 +118,13 @@ WrapSizerFrame::WrapSizerFrame()
 
         sizerMidWrap->Add(chk, wxSizerFlags().Centre().Border());
     }
-

Please avoid mixing whitespace-only changes with significant ones if possible.


In samples/wrapsizer/wrapsizer.cpp:

>      sizerMid->Add(sizerMidWrap, wxSizerFlags(100).Expand());
     sizerRoot->Add(sizerMid, wxSizerFlags(100).Expand().Border());
 
+    // A long wxStaticText that wraps like a wxWrapSizer

Really not sure this belongs to this sample anyhow. I'd rather add a button setting a long label to the "widgets" sample.


In src/common/stattextcmn.cpp:

> @@ -213,8 +213,46 @@ class wxLabelWrapper : public wxTextWrapper
 
 void wxStaticTextBase::Wrap(int width)
 {
+    if (width == m_currentWrap) return;

Please don't do this:

⬇️ Suggested change
-    if (width == m_currentWrap) return;
+    if (width == m_currentWrap)
+        return;

In src/common/stattextcmn.cpp:

> @@ -213,8 +213,46 @@ class wxLabelWrapper : public wxTextWrapper
 
 void wxStaticTextBase::Wrap(int width)
 {
+    if (width == m_currentWrap) return;
+    m_currentWrap = width;
+
+    // Allow for repeated calls to Wrap with different values
+    if (m_unwrappedLabel.IsNull()) 

Please use standard functions instead of wx 1.x ones

⬇️ Suggested change
-    if (m_unwrappedLabel.IsNull()) 
+    if (m_unwrappedLabel.empty()) 

In src/common/stattextcmn.cpp:

>      wxLabelWrapper wrapper;
     wrapper.WrapLabel(this, width);
+    InvalidateBestSize();
+}
+
+wxSize wxStaticTextBase::GetEffectiveMinSizeFirstPass() const
+{
+    // While wxWrapSizer can only wrap entire controls, a text paragraph
+    // could theoretically wrap at a few letters, so we start with
+    // requesting very little space in the first pass
+    return wxSize( 10, 10 );

Could we avoid hardcoding magic numbers please? It would make sense to use GetCharWidth() or something else based it.


In src/common/stattextcmn.cpp:

> +
+wxSize wxStaticTextBase::GetEffectiveMinSizeFirstPass() const
+{
+    // While wxWrapSizer can only wrap entire controls, a text paragraph
+    // could theoretically wrap at a few letters, so we start with
+    // requesting very little space in the first pass
+    return wxSize( 10, 10 );
+}
+
+bool wxStaticTextBase::InformFirstDirection(int direction, int size, int WXUNUSED(availableOtherDir))
+{
+    // In the second pass, this control has been given "size" amount of
+    // space in the horizontal direction. Wrap there and report a new
+    // GetEffectiveMinSize() from then on.
+
+    if (direction != wxHORIZONTAL) return false;
⬇️ Suggested change
-    if (direction != wxHORIZONTAL) return false;
+    if (direction != wxHORIZONTAL)
+        return false;

In src/common/stattextcmn.cpp:

> +    // While wxWrapSizer can only wrap entire controls, a text paragraph
+    // could theoretically wrap at a few letters, so we start with
+    // requesting very little space in the first pass
+    return wxSize( 10, 10 );
+}
+
+bool wxStaticTextBase::InformFirstDirection(int direction, int size, int WXUNUSED(availableOtherDir))
+{
+    // In the second pass, this control has been given "size" amount of
+    // space in the horizontal direction. Wrap there and report a new
+    // GetEffectiveMinSize() from then on.
+
+    if (direction != wxHORIZONTAL) return false;
+
+    int style = GetWindowStyleFlag();
+    SetWindowStyleFlag( style | wxST_NO_AUTORESIZE );

It looks like we could avoid calling it unnecessarily (and also below):

⬇️ Suggested change
-    SetWindowStyleFlag( style | wxST_NO_AUTORESIZE );
+    if ( !(style & wxST_NO_AUTORESIZE) )
+        SetWindowStyleFlag( style | wxST_NO_AUTORESIZE );

In src/common/wincmn.cpp:

> @@ -859,21 +859,39 @@ wxSize wxWindowBase::GetEffectiveMinSize() const
     // merge the best size with the min size, giving priority to the min size
     wxSize min = GetMinSize();
 
+    const wxStaticText *text = dynamic_cast<const wxStaticText*>(this);

Please remove this and all the other changes to this file.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/review/3176982057@github.com>

RobertRoeb

unread,
Sep 2, 2025, 1:54:37 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed

@RobertRoeb commented on this pull request.


In include/wx/sizer.h:

> +    // Can be overidden for two step process, e.g. by wxWrapSizer
+    virtual wxSize CalcMinFirstPass() { return CalcMin(); }

wxWrapSizer called CalcMin() several times, but with different behaviour, but instead of using either a flag or two different calls, it set a flag in wxSizer::InformFirstDirection() which altered the behaviour of CalcMin(). I tried to use CalcMinFirstPass() or CalcMin( bool firstpass = false ); for both wrapsizer and a wrapped control, but there sizers and controls are handled differently in wxSizer::Layout()


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/review/3177583041@github.com>

RobertRoeb

unread,
Sep 2, 2025, 2:01:05 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed
RobertRoeb left a comment (wxWidgets/wxWidgets#25753)

Before looking at it, could you please explain the relationship between this and wxWrapSizer?
I mean, they do conceptually similar things, but other than that, there is none, right?

The code in wxSizer() always assumed this behaviour for wxWrapSizer and wxStaticText. It just
didn't work until now. This is even documented for probably 10 years:

https://docs.wxwidgets.org/3.2/classwx_window.html#a9fd5b6520c1b30eb8e82bb5d56bc24c0

Also, I don't know if we want to do this unconditionally or if some flag is specified. While
people probably don't want their labels being truncated, they may not want them to take
more than one line too... And the interaction of this with ellipsize flags is not clear at all.

This only happens inside of a vertical sizer, which calls InformsFirstDirection() to for the sole
purpose of wrapping the text. This change makes multiline text usable in sizers and should
not have an effect otherwise.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/c3246301207@github.com>

RobertRoeb

unread,
Sep 2, 2025, 2:04:23 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Push

@RobertRoeb pushed 1 commit.


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/before/45906cc1f583b28b734393dd6e064c364e417ed3/after/ac68407fb7a522f9498044d33fb9ffbdd1018a54@github.com>

RobertRoeb

unread,
Sep 2, 2025, 2:04:58 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed

@RobertRoeb commented on this pull request.


In include/wx/window.h:

> @@ -411,6 +411,7 @@ class WXDLLIMPEXP_CORE wxWindowBase : public wxEvtHandler
         // minimum size, giving priority to the min size components, and
         // returns the results.
     virtual wxSize GetEffectiveMinSize() const;
+    virtual wxSize GetEffectiveMinSizeFirstPass() const { return  GetEffectiveMinSize(); }

Right, will do


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/review/3177610609@github.com>

RobertRoeb

unread,
Sep 2, 2025, 2:07:44 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed

@RobertRoeb commented on this pull request.


In include/wx/stattext.h:

> @@ -72,6 +77,12 @@ class WXDLLIMPEXP_CORE wxStaticTextBase : public wxControl
     // display.
     void UpdateLabel();
 
+    // save unwrapped label to allow to call Wrap() several times and 
+    // always starting from the original, unwrapped label
+    wxString m_unwrappedLabel;

I could not figure out of the stripping of markup and/or mnemonics and/or the ellipsis code would alter m_labelOrig. I think m_labelOrig is the before any change, and m_unwrappedLabel is after the other changes, but before wrapping.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/review/3177618535@github.com>

RobertRoeb

unread,
Sep 2, 2025, 2:10:55 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed

@RobertRoeb commented on this pull request.


In include/wx/stattext.h:

> @@ -72,6 +77,12 @@ class WXDLLIMPEXP_CORE wxStaticTextBase : public wxControl
     // display.
     void UpdateLabel();
 
+    // save unwrapped label to allow to call Wrap() several times and 
+    // always starting from the original, unwrapped label
+    wxString m_unwrappedLabel;
+    int m_currentWrap = 0;

0 means no wrapping (will add comment). The current value needs to be saved to prevent uncountable recalculations of the wrapping code during the wxSizer Layout() process (as you probably guessed).


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/review/3177626872@github.com>

RobertRoeb

unread,
Sep 2, 2025, 2:12:39 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Push

@RobertRoeb pushed 1 commit.


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/before/ac68407fb7a522f9498044d33fb9ffbdd1018a54/after/752ece6f3f588ea120f99d9e8e1de154d52ca7c1@github.com>

RobertRoeb

unread,
Sep 2, 2025, 4:04:57 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Push

@RobertRoeb pushed 1 commit.

  • bd0c8e8 Avoid unnecessary call of setting flag


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/before/752ece6f3f588ea120f99d9e8e1de154d52ca7c1/after/bd0c8e84796d8df38ffc0803a9fa271e67785a98@github.com>

VZ

unread,
Sep 2, 2025, 5:34:57 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed

@vadz commented on this pull request.


In include/wx/sizer.h:

> +    // Can be overidden for two step process, e.g. by wxWrapSizer
+    virtual wxSize CalcMinFirstPass() { return CalcMin(); }

wxWrapSizer was, AFAIR, somewhat of a hack and it's great to replace it with something better but this something should be documented/explained.

Maybe documenting it would also give some idea for a better name because, frankly, "first pass" is not great: it doesn't mean anything per se and implies the existence of "second pass" which doesn't actually exist.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/review/3178199506@github.com>

VZ

unread,
Sep 2, 2025, 5:36:22 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed

@vadz commented on this pull request.


In include/wx/stattext.h:

> @@ -72,6 +77,12 @@ class WXDLLIMPEXP_CORE wxStaticTextBase : public wxControl
     // display.
     void UpdateLabel();
 
+    // save unwrapped label to allow to call Wrap() several times and 
+    // always starting from the original, unwrapped label
+    wxString m_unwrappedLabel;

I think you're right, but is it really worth storing it separately instead of just stripping the other stuff from m_labelOrig?

It's not just about optimizing memory usage but also, in more places we store redundant information, more places we need to remember to update later.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/review/3178202327@github.com>

VZ

unread,
Sep 2, 2025, 5:39:45 PM (5 days ago) Sep 2
to wx-...@googlegroups.com, Subscribed
vadz left a comment (wxWidgets/wxWidgets#25753)

This only happens inside of a vertical sizer, which calls InformsFirstDirection() to for the sole purpose of wrapping the text.

Wait, does this mean that wrapping still won't work in a wxFlexGridSizer? This makes it significantly less useful and potentially very, very confusing: there are no other examples of controls (AFAIK) that appear differently depending on the sizer they're placed in.

Can we make it work for 2D sizers too?


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/c3246887197@github.com>

RobertRoeb

unread,
Sep 3, 2025, 10:27:37 AM (4 days ago) Sep 3
to wx-...@googlegroups.com, Subscribed
RobertRoeb left a comment (wxWidgets/wxWidgets#25753)

Wait, does this mean that wrapping still won't work
in a wxFlexGridSizer?

It should work there as well. I meant any vertical compartment in any sizer. And with my code it works for controls as well, although I cannot think of any apart from display of text. Well, a text editor or a HTML window, maybe.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/c3249497493@github.com>

RobertRoeb

unread,
8:51 AM (1 hour ago) 8:51 AM
to wx-...@googlegroups.com, Push

@RobertRoeb pushed 2 commits.

  • bd21c4c Added documentation for new xxx FirstPass() methods
  • 15e9d67 Document the new behaviour in wxStaticText


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/25753/before/bd0c8e84796d8df38ffc0803a9fa271e67785a98/after/15e9d672f72720e0bba074387f407b4cfe356263@github.com>

Reply all
Reply to author
Forward
0 new messages