1

I'm writing a C++ wxWidgets calculator application, and I want the font of my wxTextCtrl's and my custom buttons to scale when I resize the window.

My app

The problems are:

  1. The text in my buttons isn't always precisely in the center, but sometimes slightly off (especially in the green and red buttons)
  2. When I maximize the window, the wxTextCtrl's font size updates, but not when I minimize it, leaving it to cover half the screen until I resize the window, at which point it updates to the correct size.

I'm using this code:

text_controls.cpp

    MainText = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
    MainText->SetForegroundColour(wxColour(55, 55, 55));
    MainText->Bind(wxEVT_TEXT, &Main::OnTextChange, this);
    MainText->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
        evt.Skip();
        MainText->SetFont(
            wxFontInfo(wxSize(0, MainText->GetSize().y / 1.3))
                .Family(wxFONTFAMILY_SWISS)
                .FaceName("Lato")
                .Bold()
        );
    });

And I'm using very similar code to generate the font in my custom button class file:

ikeButton.cpp

void ikeButton::render(wxDC& dc)
{
    unsigned int w = this->GetSize().GetWidth();
    unsigned int h = this->GetSize().GetHeight();
    wxColour* bCol;

    if (pressed) {
        dc.SetBrush(*pressedBackgroundColor);
        dc.SetTextForeground(*pressedTextColor);
        bCol = pressedBorderColor;
    }
    else if (hovered) {
        dc.SetBrush(*hoveredBackgroundColor);
        dc.SetTextForeground(*hoveredTextColor);
        bCol = hoveredBorderColor;
    }
    else {
        dc.SetBrush(*backgroundColor);
        dc.SetTextForeground(*textColor);
        bCol = borderColor;
    }
    
    dc.SetPen(*wxTRANSPARENT_PEN);
    dc.DrawRectangle(0, 0, w, h);

    //bordo
    if (borderTh && bCol != NULL)
    {
        dc.SetBrush(*bCol);
        dc.DrawRectangle(0, 0, w, borderTh);
        dc.DrawRectangle(w - borderTh, 0, borderTh, h);
        dc.DrawRectangle(0, h - borderTh, w, borderTh);
        dc.DrawRectangle(0, 0, borderTh, h);
    }

    //testo
    dc.SetFont(
        wxFontInfo(wxSize(0, this->GetSize().GetHeight() / 3))
        .Family(wxFONTFAMILY_SWISS)
        .FaceName("Lato")
        .Light()
    );
    dc.DrawText(text, w / 2 - (GetTextExtent(text).GetWidth()),
        h / 2 - (GetTextExtent(text).GetHeight()));
}
iKebab897
  • 73
  • 2
  • 7

1 Answers1

2

I'm really not sure what to do about the second problem. I think setting the font size from the size event when the frame is unmaximized causes something to be done in an unexpected order and temporarily breaks wxWidgets' layout system.

The only way I've found to workaround this so far is to use WinAPI calls to inject an extra Layout call when the window is restored. To do this, add this declaration to your frame class:

#ifdef __WXMSW__
    bool MSWHandleMessage(WXLRESULT *result,WXUINT message,
                          WXWPARAM wParam, WXLPARAM lParam) override;
#endif // __WXMSW__

The body of the MSWHandleMessage method will need some extra constants that are defined in the wrapwin.h header. So in the code file for the frame, have this be the last #include line:

#ifdef __WXMSW__
    #include <wx/msw/wrapwin.h>
#endif // __WXMSW__

And then add this body for the MSWHandleMessage

#ifdef __WXMSW__
    bool MyFrame::MSWHandleMessage(WXLRESULT *result, WXUINT message,
                                   WXWPARAM wParam, WXLPARAM lParam)
    {
        if ( message == WM_SYSCOMMAND && wParam == SC_RESTORE )
        {
            CallAfter([this](){Layout();});
            // We still want to do the default processing, so do not set a result
            // and return true.
        }

        return wxFrame::MSWHandleMessage(result, message, wParam, lParam);
    }
#endif // __WXMSW__

Obviously change 'MyFrame' to the name of your frame class.


But I can help with the first one.

I think there are 2 small problems with the current calculation of where to draw the text. First, I think GetTextExtent should be called on the dc instead of the window. Second, I think there is a slight problem with order of the math operations. To center the text, I think the calculation for the x coordinate should be (w - GetTextExtent(text).GetWidth()) / 2. A similar change should be made in the calculation for the y coordinate.

I would also store the text extent calculation instead of doing it twice:

wxSize textExtent = dc.GetTextExtent(text);
dc.DrawText(text, (w - textExtent.GetWidth())/ 2,
            (h - textExtent.GetHeight())/2);

Feel free to skip this next part.

You can sometimes center text vertically better by basing the calculation on a font metric named ascent instead of the text height. Here's a diagram of what these font metrics mean from Microsoft

enter image description here

The reason ascent might be a better number to use is that the height will include several padding elements that might make the text look slightly uncentered.

wxSize textExtent = dc.GetTextExtent(text);
wxFontMetrics metrics = dc.GetFontMetrics();
dc.DrawText(text, (w - textExtent.GetWidth()) / 2, (h - metrics.ascent) / 2);
New Pagodi
  • 3,484
  • 1
  • 14
  • 24
  • A workaround for the first problem would be just to check whether the window is minimized and not change the font in this case. – VZ. Jun 05 '21 at 10:37
  • I believe they actually mean the problem happens when pressing the restore button not the minimize button. There is something about the size event when pressing the restore button that breaks the layout and makes the text control take up too much space. I've tried some workarounds using `CallAfter`, but I haven't found anything that works. – New Pagodi Jun 05 '21 at 15:26
  • I've added to the answer showing a workaround using native WinAPI calls. There has got to be a better way to do this, but I haven't found one yet. – New Pagodi Jun 07 '21 at 15:58
  • Could adding a check that the window is not minimized to the rendering code help? I am not sure in which order things happen here, but it looks like `render()` could be called before the size is updated? It would be nice to add debug prints to the code to see what exactly is going on. Or, alternatively, having a SSCCE allowing to debug it easily here. – VZ. Jun 08 '21 at 21:16