Opened 7 weeks ago

Last modified 7 weeks ago

#18531 confirmed defect

wxStaticText doesn't work for very long text

Reported by: followait Owned by:
Priority: low Milestone:
Component: wxMSW Version: dev-latest
Keywords: msw10 Cc: followait@…
Blocked By: Blocking:
Patch: no

Description

wxStaticText * pST = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
    std::ostringstream oss;
    int rows = 100;
    int cols = 100;
    for (int row = 0; row < rows; ++row)
    {
        for (int col = 0; col < cols; ++col)
            oss << "Test_" << col << '_' << row << ' ';
        oss << '\n';
    }
    pST->SetLabel(oss.str());

In this case nothing displayed in wxStaticText.
If set rows to 10 and cols to 10, it works properly.

Change History (11)

comment:1 Changed 7 weeks ago by pb101

  • Cc pbfordev@… added
  • Status changed from new to confirmed

I can confirm this.

But it seems that the native Windows control behaves the same way. SetWindowText() returns TRUE even for the too long text. The control should not have any real-world limit for text length.

In wxWidgets, the issue can be easily fixed by using any of the wxST_ELLIPSIZE_* flags.

SSCCE for wxWidgets and Win32 to reproduce the issue follows.

wxWidgets

#include <wx/wx.h>

class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        wxFrame* frame = new wxFrame(nullptr, wxID_ANY, "wxWidgets Long Static Text Test", wxDefaultPosition, wxSize(800, 600));
        wxStaticText* staticText = new wxStaticText(frame, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
        
        const int rows = 100;
        const int cols = 100;
        wxString label;
     
        for ( int row = 0; row < rows; ++row )
        {
            for ( int col = 0; col < cols; ++col )
                label << "Test_" << col << '_' << row << ' ';
            label << '\n';
        } 
        staticText->SetLabel(label);         

        frame->Show();

        return true;
    }
}; wxIMPLEMENT_APP(MyApp);

Win32

#ifdef _MSC_VER
    #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif

#include <windows.h>
#include <tchar.h>

#include <sstream>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if ( message == WM_DESTROY )
    {
        PostQuitMessage(0);
        return 0;
    }
    
    return DefWindowProc(hWnd, message, wParam, lParam);    
}

HWND CreateFrame(HINSTANCE instance)
{
    const TCHAR szWindowClass[] = _T("TestWnd");                  
    WNDCLASSEX wcex = {0};

    wcex.cbSize         = sizeof(wcex);
    wcex.lpfnWndProc    = WndProc;
    wcex.hInstance      = instance;
    wcex.hbrBackground  = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wcex.lpszClassName  = szWindowClass;
    RegisterClassEx(&wcex);

    return CreateWindow(szWindowClass, _T("Win32 Long Static Text Test"), 
              WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
              CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, 
              nullptr, nullptr, instance, nullptr);
}

HWND CreateStaticText(HWND parent, HINSTANCE instance)
{        
    RECT parentRect = {0};
    HWND hStaticText = nullptr;    

    GetClientRect(parent, &parentRect);

    hStaticText = CreateWindow(_T("STATIC"), _T(""), WS_CHILD | WS_VISIBLE | SS_LEFT,
                parentRect.left, parentRect.top, 
                parentRect.right - parentRect.left, parentRect.bottom - parentRect.top,
                parent, nullptr, instance, nullptr);

    const int rows = 100;
    const int cols = 100;
    std::ostringstream oss;
     
     for ( int row = 0; row < rows; ++row )
     {
         for (int col = 0; col < cols; ++col )
             oss << "Test_" << col << '_' << row << ' ';
         oss << '\n';
     } 
 
    bool result = SetWindowTextA(hStaticText, oss.str().c_str());
    
    return hStaticText;
}

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{      
    const HWND hFrame = CreateFrame(hInstance);
    CreateStaticText(hFrame, hInstance);

    ShowWindow(hFrame, nCmdShow);
    UpdateWindow(hFrame);

    MSG msg;
    while ( GetMessage(&msg, NULL, 0, 0) )
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}
Last edited 7 weeks ago by pb101 (previous) (diff)

comment:2 follow-up: Changed 7 weeks ago by pb101

  • Cc pbfordev@… removed

Oops, sorry in the Win32 example | SS_GRAYRECT was added accidentally and must be removed from the following line

hStaticText = CreateWindow(_T("STATIC"), _T(""), WS_CHILD | WS_VISIBLE | SS_LEFT | SS_GRAYRECT,

otherwise the control would never show any text at all.

comment:3 in reply to: ↑ 2 Changed 7 weeks ago by vadz

  • Priority changed from normal to low
  • Summary changed from wxStaticText not work for large text to wxStaticText doesn't work for very long text

Thanks for testing this, especially at Win32 level, as this shows that it's "not our problem". Of course, it doesn't mean that it isn't a problem, but, realistically, I don't see what could be done about it. Moreover, it's simple to work around it in the application code by using wxGenericStaticText -- but this can't be easily done inside the library, unfortunately, as wxStaticText really wants to be the native control. So in practice it seems very unlikely that anything will be ever done at all about it.

Replying to pb101:

Oops, sorry in the Win32 example | SS_GRAYRECT was added accidentally and must be removed from the following line

hStaticText = CreateWindow(_T("STATIC"), _T(""), WS_CHILD | WS_VISIBLE | SS_LEFT | SS_GRAYRECT,

otherwise the control would never show any text at all.

I took the liberty of editing your reply (apparently only I have the permissions to do it?) to remove this style.

comment:4 Changed 7 weeks ago by pb101

  • Cc pbfordev@… added
  • Keywords msw10 added

I tried running the very same wxWidgets and Win32 executables that did not show any text on Windows 10 also in Virtual Box Windows 7 machine. Both showed the text there, so somehow this seems to be Windows 10 related.

Setting compatibility to older Windows versions when running on Windows 10 did not help.

BTW, I actually can edit my posts but I did not notice it before. Thanks for the tip!

comment:5 Changed 7 weeks ago by awi

Isn't the root cause (string longer than 32767 pixels) the same as for #17379?

comment:6 Changed 7 weeks ago by vadz

awi, you're probably right, thanks for the reminder. Pretty weird that it works under MSW 7 though, the other bug is reproducible there too.

Anyhow, I guess we could check the text extent and truncate it automatically to fit in 2^15 pixels to display at least something in this case, which would be better than the current behaviour. OTOH using wxGenericStaticText (assuming it works) would still be a better workaround.

comment:7 Changed 7 weeks ago by followait

  • Cc followait@… added

Windows is hard, but SetWindowText should return FALSE in the case, but it doesn't.

So no sense to check the return value of SetWindowText inside wxStaticText::SetLabel.

wxStaticText::SetLabel can check the length of text before calling SetWindowText.
What about add a return value for wxStaticText::SetLabel?

Last edited 7 weeks ago by followait (previous) (diff)

comment:8 Changed 7 weeks ago by vadz

Realistically, nobody is going to check the return value of SetLabel(), so this doesn't seem very useful.

comment:9 Changed 7 weeks ago by followait

  • Cc followait@… removed

Some geek will check every return value that may fail, maybe during their early years.

I think explicity (i.e. a return value here) is always good, reason:

With a return value:

  1. The new serious programmer can simply check the return value theoretically, and no need for practical considerrations.
  1. The practical programmers can ignore the return value explicitly, and they can see they are ignoring the return value explicitly.

Without a return value, wx ignores the return value implicitly, but mislead the programmer to think that the function never fails.

I think with less policy wx will be more concise and more flexible.

Last edited 7 weeks ago by followait (previous) (diff)

comment:10 follow-up: Changed 7 weeks ago by pb101

  • Cc pbfordev@… removed

Firstly, I too find hard to believe that people would start checking the return value of wxControl::SetLabel(). However, I do believe that programmers diligent enough to do that would not use wxStaticText for displaying texts that can have (tens) of thousands of characters: they would use a read-only multi-line wxTextCtrl (with wxTE_RICH2), which is much better suited for such task.

Secondly, using string length alone is not good enough to tell if the text will be displayed or not. It seems that Win7 (VM in a VirtualBox) can handle a quite longer string than Win10, so there is more to it (see the referenced ticket). I.e, one cannot accurately tell beforehand whether the text will be displayed or not.

Thirdly, I found out that when the text is too long and nothing is displayed, ::SetWindowText() still returns TRUE but ::GetWindowTextLength() seems to return 0 instead of the length of the string passed to ::SetWindowText(). However, wxControl stores the label internally and returns it from wxControl::GetLabel(), so the wxWidgets programmer cannot use this.

comment:11 in reply to: ↑ 10 Changed 7 weeks ago by followait

  • Cc followait@… added

Replying to pb101:

Firstly, I too find hard to believe that people would start checking the return value of wxControl::SetLabel(). However, I do believe that programmers diligent enough to do that would not use wxStaticText for displaying texts that can have (tens) of thousands of characters: they would use a read-only multi-line wxTextCtrl (with wxTE_RICH2), which is much better suited for such task.

It's policy, a lib may leave the policy to users.

Secondly, using string length alone is not good enough to tell if the text will be displayed or not. It seems that Win7 (VM in a VirtualBox) can handle a quite longer string than Win10, so there is more to it (see the referenced ticket). I.e, one cannot accurately tell beforehand whether the text will be displayed or not.

Thirdly, I found out that when the text is too long and nothing is displayed, ::SetWindowText() still returns TRUE but ::GetWindowTextLength() seems to return 0 instead of the length of the string passed to ::SetWindowText(). However, wxControl stores the label internally and returns it from wxControl::GetLabel(), so the wxWidgets programmer cannot use this.

It's about implentation.
For Windows, some work arounds:

  1. hard code text limit for each version of OS
  2. simply use the lowest valid length.

For wx, source code there. Because wxControl stores window text commonly, checking validity before SetLabel seems the best solution.

Thanks for your digging.

Last edited 7 weeks ago by followait (previous) (diff)
Note: See TracTickets for help on using tickets.