Fix bug with wxRadioButton state changing unexpectedly in wxMSW

In wxMSW, a focused wxRadioButton is always checked, which meant that
checking a wxRadioButton while focus was not in the window containing it
and later giving the focus to that window could uncheck it by giving
focus to another wxRadioButton that had had it previously.

Fix this by adding WXSetPendingFocus() to wxMSW wxWindow and calling it
from wxRadioButton::SetValue() to ensure that when the focus is
regained, it goes to the newly checked radio button and not some other
one.

This replaces the previously used, for the same purpose, wxMSW-specific
wxTopLevelWindow::SetLastFocus(), so while this solution is not exactly
pretty, it's not worse than we had before, while being more generic.

Also add a unit test checking that things work correctly in the scenario
described above.

Closes https://github.com/wxWidgets/wxWidgets/pull/1257

Closes #18341.
This commit is contained in:
Vadim Zeitlin
2019-03-11 13:43:01 +01:00
parent 324c58d9e2
commit 23ddf26571
6 changed files with 103 additions and 20 deletions

View File

@@ -16,7 +16,10 @@
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/button.h"
#include "wx/panel.h"
#include "wx/radiobut.h"
#include "wx/sizer.h"
#include "wx/stattext.h"
#endif // WX_PRECOMP
@@ -195,4 +198,58 @@ void RadioButtonTestCase::Single()
CHECK(ngradio->GetValue());
}
TEST_CASE("wxRadioButton::Focus", "[radiobutton][focus]")
{
// Create a container panel just to be able to destroy all the windows
// created here at once by simply destroying it.
wxWindow* const tlw = wxTheApp->GetTopWindow();
wxScopedPtr<wxPanel> parentPanel(new wxPanel(tlw));
// Create a panel containing 2 radio buttons and another control outside
// this panel, so that we could give focus to something different and then
// return it back to the panel.
wxPanel* const radioPanel = new wxPanel(parentPanel.get());
wxRadioButton* const radio1 = new wxRadioButton(radioPanel, wxID_ANY, "1");
wxRadioButton* const radio2 = new wxRadioButton(radioPanel, wxID_ANY, "2");
wxSizer* const radioSizer = new wxBoxSizer(wxHORIZONTAL);
radioSizer->Add(radio1);
radioSizer->Add(radio2);
radioPanel->SetSizer(radioSizer);
wxButton* const dummyButton = new wxButton(parentPanel.get(), wxID_OK);
wxSizer* const sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(radioPanel, wxSizerFlags(1).Expand());
sizer->Add(dummyButton, wxSizerFlags().Expand());
parentPanel->SetSizer(sizer);
parentPanel->SetSize(tlw->GetClientSize());
parentPanel->Layout();
// Initially the first radio button should be checked.
radio1->SetFocus();
CHECK(radio1->GetValue());
CHECK(wxWindow::FindFocus() == radio1);
// Switching focus from it shouldn't change this.
dummyButton->SetFocus();
CHECK(radio1->GetValue());
// Checking another radio button should make it checked and uncheck the
// first one.
radio2->SetValue(true);
CHECK(!radio1->GetValue());
CHECK(radio2->GetValue());
// While not changing focus.
CHECK(wxWindow::FindFocus() == dummyButton);
// And giving the focus to the panel shouldn't change radio button
// selection.
radioPanel->SetFocus();
CHECK(wxWindow::FindFocus() == radio2);
CHECK(!radio1->GetValue());
CHECK(radio2->GetValue());
}
#endif //wxUSE_RADIOBTN