I'm building an app using wxWidgets. I have a wxTextCtrl created like so:
Expression = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize,
wxTE_READONLY | wxNO_BORDER | wxTE_RIGHT );
Expression->SetForegroundColour(wxColour(55, 55, 55));
Expression->Bind(wxEVT_TEXT, [this](wxCommandEvent& evt) { Expression_Update(); evt.Skip(); });
Expression->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) { Expression_Update(); evt.Skip(); });
sizer->Add(Expression, 10, wxEXPAND);
It is bind to an Expression_Update() function which is supposed to scale its font correctly.
void Main::Expression_Update()
{
wxString text = Expression->GetValue();
const int MaxWidth = GetSize().GetWidth();
int X, Y = Expression->GetSize().GetHeight();
wxFont* font = new wxFont(
wxSize(wxSize(0, Y / 1.3)),
wxFONTFAMILY_SWISS,
wxFONTSTYLE_NORMAL,
wxFONTWEIGHT_BOLD,
false,
"Calibri"
);
Expression->GetTextExtent(text, &X, nullptr, NULL, NULL, font);
if ((X + EXP_ANTI_CLIPPING_COEF) > MaxWidth)
{
do
{
int const fontHeight = font->GetPixelSize().GetHeight();
font->SetPixelSize(wxSize(0, fontHeight - 2));
Expression->GetTextExtent(text, &X, nullptr, NULL, NULL, font);
} while ((X + EXP_ANTI_CLIPPING_COEF) > MaxWidth);
}
Expression->SetFont(*font);
delete font;
}
This code works perfectly fine. However, if I add the wxTE_MULTILINE flag to the constructor:
Expression = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize,
wxTE_READONLY | wxNO_BORDER | wxTE_RIGHT | wxTE_MULTILINE );
...when I resize the window, the wxTextCtrl grows larger and larger until it fills up the entire frame.
I did some debugging myself, and I figured out the problem:
int X, Y = Expression->GetSize().GetHeight();
Without the wxTE_MULTILINE flag, Y has a starting value of 88 and changes correctly based on the control's height when I resize. But with the multiline flag set, Y starts with 88, and when I resize the window, regardless if the window height increased or not, the value changes to 164, and when I resize again, to 308, and so on.
TLDR: The GetSize().GetHeight() method returns an incorrect height value that causes the font to grow larger and larger until the text control fills up the entire window.
Related
I'm creating a wxWidgets C++ calculator application. I have a series of wxTextCtrl's for the main expression text and other stuff. They are, of course, positioned using sizers so that they grow larger or smaller as I resize the window. However, if I maximize the window, their size successfully updates, but not if I then minimize it.
My app in a normal state:
Maximized application:
And when I minimize it back:
The wxTextCtrl's sizes eventually update and correct themselves when I resize the window, but this bug is kind of annoying.
Here's the code where these controls are defined:
text_controls.cpp
#include "main.h"
void Main::AddTextControls()
{
auto sizer = new wxBoxSizer(wxVERTICAL);
Chronology = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RIGHT);
Chronology->SetBackgroundColour(wxColour(235, 235, 235));
Chronology->SetForegroundColour(wxColour(105, 105, 105));
Chronology->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
evt.Skip();
Chronology->SetFont(
wxFontInfo(wxSize(0, Chronology->GetSize().y / 1.5))
.Family(wxFONTFAMILY_SWISS)
.FaceName("Calibri Light")
);
});
sizer->Add(Chronology, 4, wxEXPAND);
Expression = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RIGHT);
Expression->SetForegroundColour(wxColour(55, 55, 55));
Expression->Bind(wxEVT_TEXT, &Main::OnTextChange, this);
Expression->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
evt.Skip();
Expression->SetFont(
wxFontInfo(wxSize(0, Expression->GetSize().y / 1.3))
.Family(wxFONTFAMILY_SWISS)
.FaceName("Calibri")
.Bold()
);
});
sizer->Add(Expression, 10, wxEXPAND);
//memoria
auto memSizer = new wxBoxSizer(wxHORIZONTAL);
MemText = new wxTextCtrl(this, wxID_ANY, "Memoria", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
MemText->SetBackgroundColour(wxColour(55, 55, 55));
MemText->SetForegroundColour(wxColour(119, 119, 119));
MemText->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
evt.Skip();
MemText->SetFont(
wxFontInfo(wxSize(0, MemText->GetSize().y / 1.3))
.Family(wxFONTFAMILY_SWISS)
.FaceName("Corbel")
);
});
memSizer->Add(MemText, 25, wxEXPAND);
Memory = new wxTextCtrl(this, wxID_ANY, "0", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RIGHT);
Memory->SetBackgroundColour(wxColour(55, 55, 55));
Memory->SetForegroundColour(wxColour(105, 105, 105));
Memory->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
evt.Skip();
Memory->SetFont(
wxFontInfo(wxSize(0, Memory->GetSize().y / 1.3))
.Family(wxFONTFAMILY_SWISS)
.FaceName("Calibri Light")
);
});
memSizer->Add(Memory, 100, wxEXPAND);
sizer->Add(memSizer, 3, wxEXPAND);
MainSizer->Add(sizer, 30, wxEXPAND);
}
Anybody got a solution? Also, I'm very new to wxWidgets.
EDIT:
Followed a suggestion and added this snippet to my main frame constructor:
Main::Main() : wxFrame(nullptr, wxID_ANY, "IKE Calculator", wxDefaultPosition, wxSize(525, 650), wxDEFAULT_FRAME_STYLE)
{
this->SetMinSize(wxSize(325, 450));
FrameSize = this->GetSize();
MainSizer = new wxBoxSizer(wxVERTICAL);
this->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
evt.Skip();
CallAfter(Layout()); //will be called later
});
this->SetBackgroundColour(wxColour(70, 73, 101));
AddTextControls();
AddMainButtons();
AddButtons();
this->SetSizer(MainSizer);
MainSizer->Layout();
}
Getting a C2064 error in VS2019: The term doesn't return a function that accepts 0 arguments.
EDIT:
Changed CallAfter(Layout()); to CallAfter(&Main::Layout); and I'm getting the same error.
Let me reword your description:
"When I change the size of the window, manually or by maximizing it, everything goes well. But when I restore the window from a maximized status then fonts are wrongly updated"
If fonts must be changed accordingly with the size of the window then the size-event must be catched in the control. The handler gets a wxSizeEvent object with the new size of the control.
==>> So use this new size (i.e. evt.GetSize()) instead of control.GetSize() in your control->Bind(wxEVT_SIZE...) lambdas.
The issue comes from the fact that wxSizer's layout-process uses the current font in the control to calculate the new size. Thus, when you receive the size-event is too late to be taken the new font-size into account.
So, when the window is resized again, with a more or less same size as the last one, then old font size is the same as the new one, and then everything matches.
What can you do?
Catch the size event for the whole window (still use your handlers for changing the font) and call 'Layout()' after the handler finishes:
MyFrame->Bind(wxEVT_SIZE, [this](wxSizeEvent& evt) {
evt.Skip();
CallAfter(Layout()); //will be called later
});
This second 'Layout` (first one is fired in sizer's code) may show a bit of flicker.
I'm writing a C++ calculator application in wxWidgets.
I want the font of all the buttons and the two wxTextCtrl's to be scaled as I resize the window. How is that done? I'm posting some code if that can help.
text_controls.cpp
#include "main.h"
void Main::AddTextControls()
{
//creazione font
wxFont Expression(10, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_LIGHT, false, "Lato");
wxFont Main(30, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_LIGHT, false, "Lato");
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
ExpText = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
ExpText->SetFont(Expression);
ExpText->SetForegroundColour(wxColour(125, 125, 125));
sizer->Add(ExpText, 1, wxEXPAND);
MainText = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
MainText->SetFont(Main);
MainText->SetForegroundColour(wxColour(55, 55, 55));
MainText->Bind(wxEVT_TEXT, &Main::OnTextChange, this);
sizer->Add(MainText, 2, wxEXPAND);
MainSizer->Add(sizer, 4, wxEXPAND);
}
You could use wxFont::SetPixelSize() to set the font size to, say, one third of the text control height. E.g.:
MainText->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) {
e.Skip();
MainText->SetFont(wxFontInfo(wxSize(0, MainText->GetSize().y / 3)).Family(wxFONTFAMILY_SWISS).FaceName("Lato").Light());
});
(which also shows using a more readable way of creating a wxFont than the ctor taking a zillion arguments).
I have created a popup using "wxPopupTransientWindow" and added some text and a button. Now I want to destroy this popup only on Clicking this particular button. Right now, Popup will get destroyed if click anywhere outside it.
Here is what I have tried so far.
SimpleTransientPopup::SimpleTransientPopup(wxWindow *parent, bool scrolled) : wxPopupTransientWindow(parent, wxFRAME_SHAPED)
{
m_bGotItClick = false;
m_panel = new wxScrolledWindow(this,-1,wxDefaultPosition,wxDefaultSize);
m_panel->SetBackgroundColour(*wxYELLOW);
wxStaticText *text = new wxStaticText( m_panel, wxID_ANY,
"This panel will guide you stepwise through the workflow\n");
wxPanel *pBottomPanel = new wxPanel(m_panel, -1);
m_button = new wxButton(pBottomPanel, Minimal_PopupButton, "Got it!",wxDefaultPosition, wxDefaultSize);
m_link = new wxHyperlinkCtrl(pBottomPanel, Minimal_PopupLink, _("Hide these tips"), _(""), wxDefaultPosition, wxDefaultSize, wxHL_ALIGN_LEFT);
m_link->SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC, wxFONTWEIGHT_NORMAL, true));
m_link->SetNormalColour(*wxLIGHT_GREY);
m_link->SetForegroundColour(*wxLIGHT_GREY);
wxBoxSizer *topSizer = new wxBoxSizer( wxVERTICAL );
wxBoxSizer *bottomSizer = new wxBoxSizer(wxHORIZONTAL);
bottomSizer->Add(m_link,0,wxALL,20);
bottomSizer->AddStretchSpacer();
bottomSizer->Add(m_button, 0, wxALL, 20);
pBottomPanel->SetAutoLayout(true);
pBottomPanel->SetSizer(bottomSizer);
bottomSizer->SetSizeHints(pBottomPanel);
bottomSizer->Fit(pBottomPanel);
topSizer->Add(text, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 20);
topSizer->Add(pBottomPanel,0,wxEXPAND);
m_panel->SetSizer( topSizer );
// Use the fitting size for the panel if we don't need scrollbars.
topSizer->Fit(m_panel);
SetClientSize(m_panel->GetSize());
wxTipKind tipKind = wxTipKind_Auto;
const int offsetY = SetTipShapeAndSize(tipKind, GetBestSize());
if (offsetY > 0)
{
// Offset our contents by the tip height to make it appear in the
// main rectangle.
topSizer->PrependSpacer(offsetY);
}
m_panel->Fit();
}
I am calling this OnDismiss function on clicking the button.
void SimpleTransientPopup::OnDismiss()
{
if (m_bGotItClick) {
wxLogMessage("%p SimpleTransientPopup::OnDismiss", this);
wxPopupTransientWindow::OnDismiss();
m_bGotItClick = false;
}
}
PS: I am new to wxWidgets
By overriding "wxPopupTransientWindow::Dismiss()" I was able to close the popup when button is clicked.
void SimpleTransientPopup::Dismiss()
{
if (m_bGotItClick) {
wxLogMessage("%p SimpleTransientPopup::Dismiss", this);
wxPopupTransientWindow::Dismiss();
m_bGotItClick = false;
}
}
I have a frame which has 4 textboxes and one link(change no of textboxes). when user clicks on link I am hiding 4th textbox. but what happens is after hiding 4th textbox, it's space remains blank. I don't want to keep its space blank. That space should disappear and frame should get resized. Is there any way that I can achieve this? when user again clicks on the link(change no of textboxes I am again displaying 4th textbox.
code:
m_ComputerNameText1 = new wxTextCtrl(m_panel, wxID_ANY, "computerName", wxDefaultPosition, wxSize(250, wxDefaultSize.GetHeight()));
m_ComputerNameText2 = new wxTextCtrl(m_panel, wxID_ANY, "computerName1", wxDefaultPosition, wxSize(250, wxDefaultSize.GetHeight()));
m_ComputerNameText3 = new wxTextCtrl(m_panel, wxID_ANY, "computerName3", wxDefaultPosition, wxSize(250, wxDefaultSize.GetHeight()));
m_ComputerNameText4 = new wxTextCtrl(m_panel, wxID_ANY, "computerName4", wxDefaultPosition, wxSize(250, wxDefaultSize.GetHeight()));
m_hyperLinkOption = new wxHyperlinkCtrl(m_panel, wxID_ANY, "Change no of textboxes", wxT(""), wxDefaultPosition, wxSize(135, wxDefaultSize.GetHeight()));
m_userPassSizer->Add(m_ComputerNameText1, 0, wxALIGN_LEFT | wxALL, 0);
m_userPassSizer->Add(m_ComputerNameText2, 0, wxALIGN_LEFT | wxALL, 0);
m_userPassSizer->Add(m_ComputerNameText3, 0, wxALIGN_LEFT | wxALL, 0);
m_userPassSizer->Add(m_ComputerNameText4, 0, wxALIGN_LEFT | wxALL, 0);
m_userPassSizer->Add(m_hyperLinkOption , 0, wxALIGN_LEFT | wxALL, 0);
Following function executes when hyperlink is clicked.
void OnAuthCodeOptionLinkClicked(wxHyperlinkEvent& event)
{
if (!m_hyperlinkOptionSelected)
{
m_hyperlinkOptionSelected= true;
m_ComputerNameText4->Hide();
m_hyperLinkOption->SetLabel("Go back");
}
else
{
m_hyperlinkOptionSelected= false;
m_ComputerNameText4->Show();
m_hyperLinkOption->SetLabel("change no of textboxes");
}
}
in this way I have to hide and show 4th textbox. After hiding some blank space remains. How can I avaoid that blank space and take link in the place of 4th textbox ?
Whenever I need to dynamically update the size of a control, I always call Layout() and Refresh().
You can find more info about Layout here:
http://docs.wxwidgets.org/3.1/classwx_window.html#a1b143c3e72bd0af533b76db4830a6113
As well as some good info on window sizing in general here:
http://docs.wxwidgets.org/3.1/overview_windowsizing.html
Calling Layout assumes that your class where you construct and manage your textboxes, etc. is a wxWindow derived control itself (like a panel, frame, whatever). If not, like you are making the changes on a view-model, you will need to call Layout and Refresh on the owning window:
void UpdateView()
{
view->Layout();
view->Refresh();
}
You may need to play around with your sizers, minimum size of windows, etc. to get the exact behavior you want.
I have an issue with spacer inside of the inner wxBoxSizer.
Here is the sample code.
class Frame : public wxFrame
{
public:
Frame()
: wxFrame(nullptr,
wxID_ANY,
wxEmptyString,
wxDefaultPosition,
wxSize(600, 200))
{
wxPanel* panel = new wxPanel(this);
// Some OUTER SIZER
wxBoxSizer* mainS = new wxBoxSizer(wxVERTICAL);
// INNER HORIZONTAL SIZER
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticText* text = new wxStaticText(panel, wxID_ANY, wxT("Some Text"));
sizer->Add(text, 0, wxALL, 5);
wxComboBox* comboBox = new wxComboBox(panel, wxID_ANY, wxT("Combo"));
sizer->Add(comboBox, 0, wxALL, 5);
// SPACER
sizer->Add(0, 0, 1, wxEXPAND, 5);
wxButton* button = new wxButton(panel, wxID_ANY, wxT("Some button"));
sizer->Add(button, 0, wxALL, 5);
mainS->Add(sizer, 0, wxALL, 5);
panel->SetSizer(mainS);
// PANEL SIZER
wxBoxSizer* panelSizer = new wxBoxSizer(wxHORIZONTAL);
panelSizer->Add(panel, 1, wxEXPAND, 5);
SetSizer(panelSizer);
Layout();
Centre(wxBOTH);
}
};
class WxguiApp
: public wxApp
{
public:
bool OnInit() override
{
Frame* w = new Frame();
w->Show();
SetTopWindow(w);
return true;
}
};
IMPLEMENT_APP(WxguiApp);
If I remove outer sizer spacer starts working fine. Is it bug in my code? Or maybe some issue with wxWidgets?
I couldn't find answer, maybe someone help? wxWidgets 3.0.2
Your inner sizer (sizer) won't expand in the horizontal direction because you didn't tell its parent sizer (mainS) to do it when adding it, so your spacer will always be 0 pixels wide, which is its initial and minimal width.
To fix this, you just need to change the mainS->Add(sizer, 0, wxALL, 5) line to use wxALL | wxEXPAND.
Additionally, I strongly encourage you to look at using wxSizerFlags instead of the cryptic syntax shown above. And, last and really least, but still worth mentioning, prefer using much more readable AddStretchSpacer() to the confusing Add(0, 0, 1, wxEXPAND, 5) call (it's confusing because expanding your spacer doesn't do anything and border size is not taken into account without any border flags).