Opened 9 years ago

Closed 20 months ago

#2438 closed defect (fixed)

wxTextCtrl margins depend on initial size

Reported by: andreas_pflug Owned by:
Priority: low Milestone: 3.0.0
Component: wxMSW Version: stable-latest
Keywords: margin edit raymond-chen-needed Cc: andreas_pflug
Blocked By: Blocking:
Patch: no

Description

The text appearance in a wxTextBox depends on the size
parameter at creation time, the attached png
demonstrates this.

The upper textbox has been created with wxDefaultSize
(from xrc without size parameter, while the lower has
been assigned a dedicated width (xrc definition
100,-1d). Later changes of size won't change the
control's behaviour.

IMHO the textbox should always appear as in the lower
example, because it looks quite ugly if a letter is
painted directly adjacent to the left edge (more
obvious for e.g. M)

Attachments (1)

textbox.png download (440 bytes) - added by andreas_pflug 9 years ago.
textbox with different creation sizes

Download all attachments as: .zip

Change History (15)

Changed 9 years ago by andreas_pflug

textbox with different creation sizes

comment:1 Changed 9 years ago by vadz

I admit I have no idea about why does it happen -- the
windows created with default and non default size looks
strictly identical from the point of view of Win32...

But contrary to what you say, it seems that the lower window
is a bug: all native text controls seem to have 2 pixels
indent and not 5 pixels.

comment:2 Changed 3 years ago by oneeyeman

You don't need an xrc to reproduce the bug.
I can see it in the textctrl branch of the widgets sample.

Just modify the WidgetsTextCtrl constructor to read as:

WidgetsTextCtrl( wxWindow *parent, wxWindowID id, const wxString &value, int flags, const wxPoint &pos = wxDefaultPosotion, const wxSize &size = wxDefaultSize)

and then when you create the control depending on whether it is a single line or multi-line use last parameter as (80,-1).

I am using TRUNK on Win7 64-bit with VS2010.

comment:3 Changed 2 years ago by vadz

  • Cc vadz removed
  • Keywords margin edit added
  • Status changed from new to confirmed
  • Summary changed from wxTextBox appearance to wxTextCtrl margins depend on initial size
  • Version set to 2.9-svn

Yes, you can just use this patch to see the bug:

  • samples/minimal/minimal.cpp

    a b MyFrame::MyFrame(const wxString& title) 
    167167    SetMenuBar(menuBar); 
    168168#endif // wxUSE_MENUS 
    169169 
     170    new wxTextCtrl(this, -1, "Foo", wxPoint(5, 5), wxSize(100, -1)); 
     171    new wxTextCtrl(this, -1, "Foo", wxPoint(5, 35), wxDefaultSize); 
     172 
    170173#if wxUSE_STATUSBAR 
    171174    // create a status bar just for fun (by default with 1 pane only) 
    172175    CreateStatusBar(2); 

I still have no idea why does it happen though, it needs to be debugged to find what exactly do we do differently when creating the first and second controls.

comment:4 Changed 2 years ago by markusj

I did a little bit of research and i think i found out what happens:

If wxDefaultSize is passed, wxControl::MSWCreateControl creates the native control with a size of (1,1):

From wxControl::MSWCreateControl:

// choose the position for the control: we have a problem with default size
// here as we can't calculate the best size before the control exists
// (DoGetBestSize() may need to use m_hWnd), so just choose the minimal
// possible but non 0 size because 0 window width/height result in problems
// elsewhere
int x = pos.x == wxDefaultCoord ? 0 : pos.x,
    y = pos.y == wxDefaultCoord ? 0 : pos.y,
    w = size.x == wxDefaultCoord ? 1 : size.x,
    h = size.y == wxDefaultCoord ? 1 : size.y;

This minimal size causes the edit control to set its internal margins to (0,0) instead of the (3,3) default. (I tested this with EM_GETMARGINS message).

Afterwards the control gets resized, but the internal margins are not affected by this.

The margins can be (re-)set by calling:
::SendMessage((HWND)GetHandle(), EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, -1);

(This is not documented though, i found this by experiment).

Possible fix could be to put this line into wxTextCtrl::SetSize().

comment:5 follow-up: Changed 2 years ago by vadz

  • Milestone set to 3.0

Excellent, thanks a lot for debugging this!

I think that it might be simpler/better to increase the size used in MSWCreateControl(), it shouldn't be a problem to use something larger (e.g. 20*20 used in some other places in wx already I believe) as we call SetInitialSize() soon after anyhow.

Would it be possible for you to test whether doing this fixes the problem (it should) and, more importantly, doesn't result in any other problems?

Thanks again!

comment:6 in reply to: ↑ 5 Changed 2 years ago by markusj

Replying to vadz:

I think that it might be simpler/better to increase the size used in MSWCreateControl(), it shouldn't be a problem to use something larger (e.g. 20*20 used in some other places in wx already I believe) as we call SetInitialSize() soon after anyhow.

No, that doesn't work. The margins will be reduced when there is not enough room for content + margins. I.e. we see the effect not only when using wxDefaultSize, but whenever a size is given that is too small for the text to fit.

comment:7 follow-up: Changed 2 years ago by vadz

I see, thanks. Then we indeed need to add EM_SETMARGINS call to either wxTextCtrl::MSWCreateText() or (overridden) wxTextCtrl::DoSetSize().

Would calling it in the latter automatically decrease the margins if the control becomes too small for its contents? If so, this is what we should do as this looks like a useful behaviour.

If you could please make a patch doing this, it would be great.

TIA!

comment:8 in reply to: ↑ 7 Changed 2 years ago by markusj

Replying to vadz:

Would calling it in the latter automatically decrease the margins if the control becomes too small for its contents? If so, this is what we should do as this looks like a useful behaviour.

No. Actually some of me previous conclusions were wrong.

After two more hours of debugging i'm still not sure how to handle this ;)

The behavior is independent of the content of the control. It only depends on the initial size, but the systematic behind it is unclear.

Some examples:
When initially creating the control with a size of (3,3) i get margins of (0,0).
With (4,4) i get margins of (3,3)
With (5,5) i get margins of (0,0)

And this also changes depending on the current font size.

I tested all combination from -1 to 32 for width and height, the result were pretty strange, i think it's not necessary to get into more detail now.

I see two possible fixes here, both in /msw/control.cpp

a) After setting the final size, send EM_SETMARGINS message. This will fix the issue in most cases, but it can still happen when the user explicitly sets the size to an "unlucky" value, like (20, 20).

    // set the size now if no initial size specified
    SetInitialSize(size);

#if wxUSE_TEXTCTRL
    if ( wxDynamicCastThis(wxTextCtrl) )
    {
      // give the edit control the chance to re-adjust its internal margins
      ::SendMessage(m_hWnd, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, -1);
    }
#endif // wxUSE_TEXTCTRL

b) Always create the control with CW_USEDEFAULT for width and height. In this case the textcontrol *always* gets the correct margins.

Question is: Why is CW_USEDEFAULT not used right now?

Although i made a test where CW_USEDEFAULT is always used for all controls, i'd rather limit it to wxTextCtrl to make sure that it doesn't silently break anything else.

    int x = pos.x == wxDefaultCoord ? 0 : pos.x,
        y = pos.y == wxDefaultCoord ? 0 : pos.y,
        w = size.x == wxDefaultCoord ? 1 : size.x,
        h = size.y == wxDefaultCoord ? 1 : size.y;

#if wxUSE_TEXTCTRL
    if ( wxDynamicCastThis(wxTextCtrl) )
    {
       // always create edit control with default sizes,
       // so that it gets proper internal margins
       w = CW_USEDEFAULT;
       h = CW_USEDEFAULT;
    }
#endif // wxUSE_TEXTCTRL

These are just code samples for discussion, i can create a proper patch if needed.

comment:9 follow-up: Changed 2 years ago by vadz

  • Keywords raymond-chen-needed added

CW_USEDEFAULT is invalid for controls, according to the MSDN documentation of `CreateWindow()`

CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is specified for a pop-up or child window, nWidth and nHeight are set to zero.

So it doesn't look like a good idea to use it.

But I'm pretty surprised by the behaviour you found too and don't have any explanation for it neither. Under which system did you test this? It would be interesting to do the tests under other Windows versions too.

comment:10 in reply to: ↑ 9 Changed 2 years ago by markusj

Replying to vadz:

But I'm pretty surprised by the behaviour you found too and don't have any explanation for it neither. Under which system did you test this? It would be interesting to do the tests under other Windows versions too.

I tested under XP. I made another test under Windows7/64 now, and - of course - it behaves totally different, but at least consistent. The margins are always (0,0), no matter what you do. (Although they can still be set to a higher value with EM_SETMARGINS message).

If we can't use CW_USEDEFAULT for the initial size, (32, 32) would be my next best choice. At these dimensions, we get always get the default margins, even with the "big font" desktop theme enabled.

comment:11 follow-up: Changed 2 years ago by vadz

Weird, I don't understand the part about zero margins under Win7 -- with my diff from comment:3 I definitely saw non-zero margins for the first control and I tested under Win7/64 too. Are the margins really 0 in this case for you?

comment:12 in reply to: ↑ 11 Changed 2 years ago by markusj

Replying to vadz:

Weird, I don't understand the part about zero margins under Win7 -- with my diff from comment:3 I definitely saw non-zero margins for the first control and I tested under Win7/64 too. Are the margins really 0 in this case for you?

I found out that the Aero theme changes the behavior. I had Aero disabled and all margins were 0.

Testing with Aero, i see the problem again, the results are almost identical to XP.

comment:13 Changed 20 months ago by vadz

I'm going to explicitly set the margins to EC_USEFONTINFO on creation just to solve this somehow. Here is an updated version of the test code I used just in case anybody else wants to play with it:

  • samples/minimal/minimal.cpp

    diff --git a/samples/minimal/minimal.cpp b/samples/minimal/minimal.cpp
    index a78e462..f48e7be 100644
    a b bool MyApp::OnInit() 
    172172    CreateStatusBar(2); 
    173173    SetStatusText("Welcome to wxWidgets!"); 
    174174#endif // wxUSE_STATUSBAR 
     175 
     176    static const struct TextTest 
     177    { 
     178        const char* text; 
     179        int width; 
     180    } tts[] = 
     181    { 
     182        { "Foo", 200 }, 
     183        { "Foo",  -1 }, 
     184        { "",    200 }, 
     185        { "",     -1 }, 
     186        { "Very very very very very long", 200 }, 
     187        { "Very very very very very long", -1 }, 
     188    }; 
     189 
     190    for ( unsigned n = 0; n < WXSIZEOF(tts); n++ ) 
     191    { 
     192        const TextTest& tt = tts[n]; 
     193        wxTextCtrl* t = new wxTextCtrl(this, -1, tt.text, 
     194                                       wxPoint(5, 5+30*n), 
     195                                       wxSize(tt.width, -1)); 
     196        t->SetValue(wxString::Format("Text #%u: margin is %d", 
     197                                     n, t->GetMargins().x)); 
     198    } 
    175199} 
    176200 
    177201 

comment:14 Changed 20 months ago by VZ

  • Resolution set to fixed
  • Status changed from confirmed to closed

(In [73126]) Explicitly set margins for single line text controls in wxMSW.

The margin used by them was inconsistent and depended on the initial size the
control was created with for some reason. Call EM_SETMARGINS explicitly to
ensure consistent appearance in all cases.

Closes #2438.

Note: See TracTickets for help on using tickets.