Несколько раз пытался начать писать эту статью, но находил все новые и новые варианты доработки примера к ней, поэтому времени ушло много. Надеюсь, материал, изложенный ниже, принесет кому-нибудь пользу.
В этот раз я хочу рассказать о таком полезном явлении, как привязка данных и проверка вводимых значений.
Многие из нас помнят, какими неудобными были установка и получение значений, отображаемых элементами управления, с использованием WinAPI. Все эти GetWindowText/SetWindowText
с последующей проверкой значений вручную… это ведь так долго. На это уходит столько времени.
К счастью для нас, в wxWidgets для обеспечения привязки данных и их автоматического отображения существуют специальная группа классов, называемых валидаторами (Validators).
Большинство элементов управления в wxWidgets поддерживают обмен данными через валидаторы. В wxWidgets существует механизм привязки данных к стандартным элементам управления (такие как wxTextCtrl
, wxListBox
, wxListCtrl
, wxSpinCtrl
и др.). Для этой цели используются классы wxGenericValidator
и wxTextValidator
.
С помощью класса wxGenericValidator
возможна привязка данных типа int
, bool
, wxString
, wxArrayInt
(wxArrayInt
используется для получения списка выделенных элементов например в wxListBox
).
Для того, чтобы обеспечить привязку данных к элементу управления, необходимо
– Создать переменную-член класса формы
– После того, как элемент управления создан, привязать к нему данные с помощью метода SetValidator
– Валидатор можно задать и при создании элемента управления (в качестве параметра конструктора или метода Create
)
Делается это довольно просто
class MyDialog : public wxDialog { double m_Width; ... }; ... wxTextCtrl width_textctrl = new wxTextCtrl(this, ID_QUANTITY_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxSize(150,-1), 0, wxFPValidator(&m_Width));
Давайте создадим небольшое тестовое приложение и посмотрим, как работает механизм привязки данных.
Application.h
#ifndef _APPLICATION_H #define _APPLICATION_H #include <wx/wx.h> class MyApp : public wxApp { wxImageList m_ImageList; public: virtual bool OnInit(); wxImageList & GetImageList() { return m_ImageList; } }; DECLARE_APP(MyApp); #endif
Application.cpp
#include "Application.h" #include "MainFrame.h" #include <wx/image.h> IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { wxImage::AddHandler(new wxXPMHandler); m_ImageList.Create(15, 15); m_ImageList.Add(wxBitmap(wxT("package_ok.xpm"), wxBITMAP_TYPE_XPM)); m_ImageList.Add(wxBitmap(wxT("package_error.xpm"), wxBITMAP_TYPE_XPM)); MainFrame * frame = new MainFrame; SetTopWindow(frame); frame->Centre(); frame->Show(); return true; }
MainFrame.h
#ifndef _MAIN_FRAME_H #define _MAIN_FRAME_H #include <wx/wx.h> class MainFrame : public wxFrame { public: MainFrame(); DECLARE_EVENT_TABLE(); void OnShowStandardValidators(wxCommandEvent & event); }; #endif
MainFrame.cpp
#include "MainFrame.h" #include "DefaultValidatorsDialog.h" enum { ID_MANUAL_VALIDATION_BUTTON = 10001, ID_STANDARD_VALIDATORS_BUTTON, ID_EXTENDED_VALIDATORS_BUTTON }; BEGIN_EVENT_TABLE(MainFrame, wxFrame) EVT_BUTTON(ID_MANUAL_VALIDATION_BUTTON, MainFrame::OnShowManualValidation) EVT_BUTTON(ID_STANDARD_VALIDATORS_BUTTON, MainFrame::OnShowStandardValidators) EVT_BUTTON(ID_EXTENDED_VALIDATORS_BUTTON, MainFrame::OnShowExtendedValidators) END_EVENT_TABLE() MainFrame::MainFrame() : wxFrame(NULL, wxID_ANY, _("wxFPValidator Test"), wxDefaultPosition, wxDefaultSize, wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU|wxRAISED_BORDER) { #ifdef __WXMSW__ SetIcon(wxIcon(wxT("MAIN_ICON"), wxBITMAP_TYPE_ICO_RESOURCE)); #else SetIcon(wxIcon(wxT("wxwin.ico"), wxBITMAP_TYPE_ICO)); #endif wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL); SetSizer(sizer); wxButton * standard_validators_button = new wxButton(this, ID_STANDARD_VALIDATORS_BUTTON, _("Standard Validators")); sizer->Add(standard_validators_button, 0, wxGROW|wxALL, 5); sizer->Fit(this); SetMinSize(GetSize()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); } void MainFrame::OnShowStandardValidators(wxCommandEvent & event) { DefaultValidatorsDialog * dlg = new DefaultValidatorsDialog(this); if(dlg->ShowModal() == wxID_OK) { wxString result; double salary(0); if(!dlg->GetSalary().ToDouble(&salary)) { wxMessageBox(wxT("Illagal value of 'Salary' field")); return; } result = wxString::Format( _("Name = %s\r\nAge = %i\r\nSalary = %1.2f\r\nMarital Status = %s\r\nSkills = "), dlg->GetName().GetData(), dlg->GetAge(), salary, dlg->GetMaritalStatus() ? wxT("married") : wxT("not married")); wxString skill_list; for(size_t i = 0; i < dlg->GetSkills().Count(); i++) { if(!skill_list.IsEmpty()) skill_list += wxT(","); skill_list += wxString::Format( wxT("%i"), dlg->GetSkills()[i]); } result += skill_list; wxMessageBox(result); } dlg->Destroy(); }
DefaultValidatorsDialog.h
#ifndef _DEFAULT_VALIDATORS_DIALOG_H #define _DEFAULT_VALIDATORS_DIALOG_H #include <wx/wx.h> #define DefaultValidatorsDialogName _("Default Validators") class DefaultValidatorsDialog : public wxDialog { wxString m_Name; int m_Age; wxString m_Salary; bool m_Married; wxArrayInt m_Skills; void CreateControls(); public: DefaultValidatorsDialog(); DefaultValidatorsDialog(wxWindow * parent, wxWindowID id = wxID_ANY, const wxString title = DefaultValidatorsDialogName); bool Create(wxWindow * parent, wxWindowID id = wxID_ANY, const wxString title = DefaultValidatorsDialogName); wxString GetName() {return m_Name;} int GetAge() {return m_Age;} wxString GetSalary() {return m_Salary;} bool GetMaritalStatus() {return m_Married;} wxArrayInt & GetSkills() {return m_Skills;} }; #endif
DefaultValidatorsDialog.cpp
#include "DefaultValidatorsDialog.h" #include <wx/textctrl.h> #include <wx/spinctrl.h> #include <wx/checkbox.h> #include <wx/listbox.h> #include <wx/valgen.h> enum { ID_NAME_TEXTCTRL = 10001, ID_AGE_SPINCTRL, ID_SALARY_TEXTCTRL, ID_MARITAL_CHECKBOX, ID_SKILLS_LISTBOX }; DefaultValidatorsDialog::DefaultValidatorsDialog() { } DefaultValidatorsDialog::DefaultValidatorsDialog(wxWindow * parent, wxWindowID id, const wxString title) { Create(parent, id, title); } bool DefaultValidatorsDialog::Create(wxWindow * parent, wxWindowID id, const wxString title) { bool res = wxDialog::Create(parent, id, title); if(res) { m_Name = wxEmptyString; m_Age = 20; m_Married = false; m_Skills.Clear(); CreateControls(); Centre(); } return res; } void DefaultValidatorsDialog::CreateControls() { wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL); SetSizer(sizer); wxFlexGridSizer * flexgrid_sizer = new wxFlexGridSizer(2,5); flexgrid_sizer->AddGrowableCol(1); wxStaticText * name_label = new wxStaticText(this, wxID_ANY, _("Name:")); wxStaticText * age_label = new wxStaticText(this, wxID_ANY, _("Age:")); wxStaticText * salary_label = new wxStaticText(this, wxID_ANY, _("Salary:")); wxStaticText * marital_label = new wxStaticText(this, wxID_ANY, _("Marital Status:")); wxStaticText * skills_label = new wxStaticText(this, wxID_ANY, _("Skills:")); wxTextCtrl * name_textctrl = new wxTextCtrl(this, ID_NAME_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxSize(150,-1)); wxSpinCtrl * age_spinctrl = new wxSpinCtrl(this, ID_AGE_SPINCTRL); wxTextCtrl * salary_textctrl = new wxTextCtrl(this, ID_SALARY_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxSize(150,-1)); wxCheckBox * marital_checkbox = new wxCheckBox(this, ID_MARITAL_CHECKBOX, wxT("Married")); wxArrayString skills; skills.Add(wxT("C/C++")); skills.Add(wxT("C#")); skills.Add(wxT("Delphi")); skills.Add(wxT("Visual Basic")); skills.Add(wxT("ASP.NET")); skills.Add(wxT("SQL")); wxListBox * skills_listbox = new wxListBox(this, ID_SKILLS_LISTBOX, wxDefaultPosition, wxDefaultSize, skills, wxLB_MULTIPLE); name_textctrl->SetValidator(wxGenericValidator(&m_Name)); age_spinctrl->SetValidator(wxGenericValidator(&m_Age)); salary_textctrl->SetValidator(wxGenericValidator(&m_Salary)); marital_checkbox->SetValidator(wxGenericValidator(&m_Married)); skills_listbox->SetValidator(wxGenericValidator(&m_Skills)); flexgrid_sizer->Add(name_label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(name_textctrl, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flexgrid_sizer->Add(age_label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(age_spinctrl, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flexgrid_sizer->Add(salary_label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(salary_textctrl, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flexgrid_sizer->Add(marital_label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(marital_checkbox, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flexgrid_sizer->Add(skills_label, 0, wxALIGN_LEFT|wxALIGN_TOP|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(skills_listbox, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); sizer->Add(flexgrid_sizer, 0, wxEXPAND|wxALL, 10); wxBoxSizer * button_sizer = new wxBoxSizer(wxHORIZONTAL); wxButton * ok_button = new wxButton(this, wxID_OK, _("OK")); wxButton * cancel_button = new wxButton(this, wxID_CANCEL, _("Cancel")); button_sizer->Add(ok_button, 0, wxLEFT|wxBOTTOM, 5); button_sizer->Add(cancel_button, 0, wxLEFT|wxRIGHT|wxBOTTOM, 5); sizer->Add(button_sizer, 0, wxALIGN_CENTER|wxBOTTOM, 5); sizer->Fit(this); }
У нас должно получиться что-то подобное:
- Name – привязка строковой переменной
- Age – привязка целочисленной переменной
- Salary – к сожалению wxWidgets не предоставляет возможности привязать к элементу управления переменную типа
double
, поэтому мы делаем привязку строковой переменной и преобразуем ее в double после закрытия окна диалога. - Marital Status – привязка переменной типа
bool
- Skills – привязка массива индексов
Давайте проанализируем, что же у нас получилось.
1. В поле Name и Age ведем любое значение, в поле Salary введем корректное значение для типа double (например 123.25), отметим флаг Marital Status, выделим несколько элементов в списке Skills.
Теперь жмем OK и наблюдаем результат
2. Теперь проделаем те же действия, но в поле Salary введем заведомо некорректное значение
Жмем ОК и смотрим, что же у нас получилось.
Данное сообщение мы имеем возможность лицезреть только потому, что мы самостоятельно в тексте программы делаем проверку корректности преобразования wxString
в double
double salary(0); if(!dlg->GetSalary().ToDouble(&salary)) { wxMessageBox(wxT("Illagal value of 'Salary' field")); return; }
И только так мы можем узнать о том, что пользователь ввел некорректное значение.
Из этого следует вывод: стандартные валидаторы wxWidgets обеспечивают привязку данных, но не обеспечивают их проверку в процессе ввода данных, что само по себе не очень удобно т.к. требует написания кода проверки значения для каждой переменной.
Что можно сделать? Можно немного «доработать напильником» и избавить себя от большого количества проблем в будущем. Этим мы сейчас и займемся.
Под «этим» я имею в виду написание собственного валидатора с поддкржкой типа double.
wxFPValidator.h
#ifndef _WX_FP_VALIDATOR_H #define _WX_FP_VALIDATOR_H #include <wx/wx.h> #include </validate.h> class wxFPValidator : public wxValidator { protected: double * m_pDouble; int * m_pInt; wxString * m_pString; void Initialize(); public: wxFPValidator(double * valPtr); wxFPValidator(int * valPtr); wxFPValidator(wxString * valPtr); wxFPValidator(const wxFPValidator & validator); ~wxFPValidator(); virtual wxValidator* Clone() const; virtual bool TransferFromWindow(); virtual bool TransferToWindow(); virtual bool Validate(wxWindow * parent); }; #endif
Описание методов:
- TransferToWindow – предназначен для отображения значения переменной в элементе управления
- TransfedFromWindow – предназначен для получения значения из элемента управления в переменную
- Validate – предназначен для проверки значения введеного пользователем
Метод TransferToWindow
вызывается когда мы в нашей программе после установки значений переменных вызываем TransferDataToWindow>/code? чтобы отобразить значения в элементах управления. Например
m_Name = wxEmptyString; m_Age = 20; m_Married = false; m_Skills.Clear(); TransferDataToWindow();
Метод TransferToWindow
вызывается когда мы в нашей программе вызываем TransferDataFromWindow
чтобы получить значения, введенные пользователем.
При работе с диалогами, после закрытия диалогового окна, введенные значения автоматически записываются в переменные, ассоциированные с элементами управления.
DefaultValidatorsDialog * dlg = new DefaultValidatorsDialog(this); if(dlg->ShowModal() == wxID_OK) { wxString result; double salary(0); if(!dlg->GetSalary().ToDouble(&salary)) { wxMessageBox(wxT("Illagal value of 'Salary' field")); return; } ... }
wxFPValidator.cpp
#include </wxFPValidator/wxFPValidator.h> #include <math.h> wxFPValidator::wxFPValidator(double * valPtr) { Initialize(); m_pDouble = valPtr; } wxFPValidator::wxFPValidator(int * valPtr) { Initialize(); m_pInt = valPtr; } wxFPValidator::wxFPValidator(wxString * valPtr) { Initialize(); m_pString = valPtr; } wxFPValidator::wxFPValidator(const wxFPValidator & validator) : m_pDouble(validator.m_pDouble), m_pInt(validator.m_pInt), m_pString(validator.m_pString) { } wxFPValidator::~wxFPValidator() { } void wxFPValidator::Initialize() { m_pDouble = NULL; m_pInt = NULL; m_pString = NULL; } wxValidator* wxFPValidator::Clone() const { return new wxFPValidator(*this); } bool wxFPValidator::TransferFromWindow() { if(!m_validatorWindow) { return false; } #if wxUSE_TEXTCTRL if (m_validatorWindow->IsKindOf(CLASSINFO(wxTextCtrl)) ) { wxTextCtrl * pControl = (wxTextCtrl*) m_validatorWindow; if(m_pDouble) { return pControl->GetValue().ToDouble(m_pDouble); } else if(m_pInt) { double tmp; bool res = pControl->GetValue().ToDouble(&tmp); if(tmp >= INT_MIN && tmp <= INT_MAX && (fmod(tmp,1) == 0)) { *m_pInt = (int)tmp; } else res = false; return res; } else if(m_pString) { wxString tmp = pControl->GetValue(); if(!tmp.IsEmpty()) { *m_pString = tmp; return true; } return false; } } #endif return false; } bool wxFPValidator::TransferToWindow() { if(!m_validatorWindow) { return false; } #if wxUSE_TEXTCTRL if (m_validatorWindow->IsKindOf(CLASSINFO(wxTextCtrl)) ) { wxTextCtrl * pControl = (wxTextCtrl*) m_validatorWindow; if(m_pDouble) { pControl->SetValue(wxString::Format(wxT("%1.2f"), *m_pDouble)); return true; } else if(m_pInt) { pControl->SetValue(wxString::Format(wxT("%i"), *m_pInt)); return true; } else if(m_pString) { pControl->SetValue(*m_pString); return true; } } #endif return false; } bool wxFPValidator::Validate(wxWindow * parent) { if(!m_validatorWindow) { return false; } #if wxUSE_TEXTCTRL if (m_validatorWindow->IsKindOf(CLASSINFO(wxTextCtrl)) ) { wxTextCtrl * pControl = (wxTextCtrl*) m_validatorWindow; if(m_pDouble != NULL) { return pControl->GetValue().ToDouble(m_pDouble); } else if(m_pInt != NULL) { double tmp; bool res = pControl->GetValue().ToDouble(&tmp); if(tmp >= INT_MIN && tmp <= INT_MAX && (fmod(tmp,1) == 0)) { *m_pInt = (int)tmp; } else res = false; return res; } else if(m_pString != NULL) { return !pControl->GetValue().IsEmpty(); } } #endif return false; }
Как видно из исходного кода, наш валидатор позволяет выполнять привязку переменных типа wxString
, int и double
.
Обработка выполняется только для элементов управления типа wxTextCtrl
. Для этого существует соответствующая проверка с помощью RTTI
if (m_validatorWindow->IsKindOf(CLASSINFO(wxTextCtrl)) ) { wxTextCtrl * pControl = (wxTextCtrl*) m_validatorWindow; if(m_pDouble != NULL) { return pControl->GetValue().ToDouble(m_pDouble); } ... }
Для других типов элементов управления соответветствующий функционал необходимо писать самостоятельно (или же посмотреть реализацию класса wxGenericValidator
и взять исходный код оттуда).
Хотелось бы заметить, что для стандартного wxGenericValidator
метод Validate всегда возвращает true не зависимо от того, коррктно ли введенное значение. Функционал проверки значений просто не реализован и его реализация ложится на плечи разработчика (т.е. на наши с вами).
Теперь у нас есть валидатор с привязкой переменных типа double
да еще и с проверкой введенного значения. Но было бы неплохо организовать проверку прямо при вводе данных. Это намного удобнее чем выдавать сообщение об ошибке уже после закрытия окна ввода (такая ситуация особенно неприятна при заполнении больших форм).
MainFrame.h
#ifndef _MAIN_FRAME_H #define _MAIN_FRAME_H #include </wx.h> class MainFrame : public wxFrame { ... void OnShowManualValidation(wxCommandEvent & event); ... }; #endif
MainFrame.cpp
... #include "ManualValidationDialog.h" ... BEGIN_EVENT_TABLE(MainFrame, wxFrame) ... EVT_BUTTON(ID_MANUAL_VALIDATION_BUTTON, MainFrame::OnShowManualValidation) ... END_EVENT_TABLE() ... MainFrame::MainFrame() : wxFrame(NULL, wxID_ANY, _("wxFPValidator Test"), wxDefaultPosition, wxDefaultSize, wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU|wxRAISED_BORDER) { ... wxButton * manual_validation_button = new wxButton(this, ID_MANUAL_VALIDATION_BUTTON, _("Extended Validators - Manual Validation")); sizer->Add(manual_validation_button, 0, wxGROW|wxALL, 5); ... } ... void MainFrame::OnShowManualValidation(wxCommandEvent & event) { ManualValidationDialog * dlg = new ManualValidationDialog(this); if(dlg->ShowModal()) { } dlg->Destroy(); }
ManualValidationDialog.h
#ifndef _MANUAL_VALIDATION_DIALOG_H #define _MANUAL_VALIDATION_DIALOG_H #include </wx.h> #define ManualValidationDialogName _("Manual Validation") class ManualValidationDialog : public wxDialog { double m_Width; double m_Height; int m_Quantity; wxStaticBitmap * m_WidthStatusBitmap; wxStaticBitmap * m_HeightStatusBitmap; wxStaticBitmap * m_QuantityStatusBitmap; wxTextCtrl * m_WidthTextCtrl; wxTextCtrl * m_HeightTextCtrl; wxTextCtrl * m_QuantityTextCtrl; void CreateControls(); public: ManualValidationDialog(); ManualValidationDialog(wxWindow * parent, wxWindowID id = wxID_ANY, const wxString title = ManualValidationDialogName); bool Create(wxWindow * parent, wxWindowID id = wxID_ANY, const wxString title = ManualValidationDialogName); double GetWidth() {return m_Width;} double GetHeight() {return m_Height;} int GetQuantity() {return m_Quantity;} DECLARE_EVENT_TABLE(); void OnTextCtrlUpdate(wxCommandEvent & event); void OnSubmitButtonUpdateUI(wxUpdateUIEvent & event); void OnSubmit(wxCommandEvent & event); }; #endif
ManualValidationDialog.cpp
#include "ManualValidationDialog.h" #include </imaglist.h> #include </valgen.h> #include </wxFPValidator/wxFPValidator.h> #include "Application.h" enum { ID_WIDTH_TEXTCTRL = 10001, ID_HEIGHT_TEXTCTRL, ID_QUANTITY_TEXTCTRL, ID_WIDTH_STATIC_BITMAP, ID_HEIGHT_STATIC_BITMAP, ID_QUANTITY_STATIC_BITMAP, ID_SUBMIT_BUTTON, }; BEGIN_EVENT_TABLE(ManualValidationDialog, wxDialog) EVT_TEXT(ID_WIDTH_TEXTCTRL, ManualValidationDialog::OnTextCtrlUpdate) EVT_TEXT(ID_HEIGHT_TEXTCTRL, ManualValidationDialog::OnTextCtrlUpdate) EVT_TEXT(ID_QUANTITY_TEXTCTRL, ManualValidationDialog::OnTextCtrlUpdate) EVT_UPDATE_UI(ID_SUBMIT_BUTTON, ManualValidationDialog::OnSubmitButtonUpdateUI) EVT_BUTTON(ID_SUBMIT_BUTTON, ManualValidationDialog::OnSubmit) END_EVENT_TABLE() ManualValidationDialog::ManualValidationDialog(wxWindow * parent, wxWindowID id, const wxString title) { m_Width = 1024; m_Height = 768; m_Quantity = 10; Create(parent, id, title); } bool ManualValidationDialog::Create(wxWindow * parent, wxWindowID id, const wxString title) { bool res = wxDialog::Create(parent, id, title); if(res) { SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY); CreateControls(); Centre(); } return res; } void ManualValidationDialog::CreateControls() { wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL); SetSizer(sizer); wxFlexGridSizer * flex_grid_sizer = new wxFlexGridSizer(2, 3, 2, 2); wxStaticText * label_width = new wxStaticText(this, wxID_ANY, _("Width:")); wxStaticText * label_height = new wxStaticText(this, wxID_ANY, _("Height:")); wxStaticText * label_quantity = new wxStaticText(this, wxID_ANY, _("Quantity:")); m_WidthTextCtrl = new wxTextCtrl(this, ID_WIDTH_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxSize(150,-1), 0, wxFPValidator(&m_Width)); m_HeightTextCtrl = new wxTextCtrl(this, ID_HEIGHT_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxSize(150,-1), 0, wxFPValidator(&m_Height)); m_QuantityTextCtrl = new wxTextCtrl(this, ID_QUANTITY_TEXTCTRL, wxEmptyString, wxDefaultPosition, wxSize(150,-1), 0, wxFPValidator(&m_Quantity)); wxBitmap tmp_bitmap(15, 15); m_WidthStatusBitmap = new wxStaticBitmap(this, ID_WIDTH_STATIC_BITMAP, tmp_bitmap); m_HeightStatusBitmap = new wxStaticBitmap(this, ID_HEIGHT_STATIC_BITMAP, tmp_bitmap); m_QuantityStatusBitmap = new wxStaticBitmap(this, ID_HEIGHT_STATIC_BITMAP, tmp_bitmap); flex_grid_sizer->Add(label_width, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flex_grid_sizer->Add(m_WidthTextCtrl, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flex_grid_sizer->Add(m_WidthStatusBitmap, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5); flex_grid_sizer->Add(label_height, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flex_grid_sizer->Add(m_HeightTextCtrl, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flex_grid_sizer->Add(m_HeightStatusBitmap, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5); flex_grid_sizer->Add(label_quantity, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flex_grid_sizer->Add(m_QuantityTextCtrl, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flex_grid_sizer->Add(m_QuantityStatusBitmap, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5); flex_grid_sizer->AddGrowableCol(1); sizer->Add(flex_grid_sizer, 0, wxEXPAND); wxButton * submit_button = new wxButton(this, ID_SUBMIT_BUTTON, _("Submit")); sizer->Add(submit_button, 0, wxGROW|wxALL, 5); sizer->Fit(this); SetMinSize(GetSize()); } void ManualValidationDialog::OnTextCtrlUpdate(wxCommandEvent & event) { wxWindow * target = wxDynamicCast(event.GetEventObject(), wxWindow); int bitmapIndex(0); wxStaticBitmap * static_bitmap(NULL); if(target) { wxValidator * validator = target->GetValidator(); if(validator) { if(!validator->Validate(target)) { bitmapIndex = 1; } switch(target->GetId()) { case ID_WIDTH_TEXTCTRL: static_bitmap = m_WidthStatusBitmap; break; case ID_HEIGHT_TEXTCTRL: static_bitmap = m_HeightStatusBitmap; break; case ID_QUANTITY_TEXTCTRL: static_bitmap = m_QuantityStatusBitmap; break; default: wxLogTrace(wxTraceMask(), _("Illegal call of ManualValidationDialog::OnTextCtrlUpdate")); break; } if(static_bitmap) { static_bitmap->SetBitmap( wxGetApp().GetImageList().GetBitmap(bitmapIndex)); } } } } void ManualValidationDialog::OnSubmitButtonUpdateUI(wxUpdateUIEvent & event) { event.Enable(Validate()); } void ManualValidationDialog::OnSubmit(wxCommandEvent & event) { if(TransferDataFromWindow()) { wxString message = wxString::Format( wxT("Width = %1.2f\r\nHeight = %1.2f\r\nQuantity = %i"), m_Width, m_Height, m_Quantity); wxMessageBox(message); } }
Теперь пробуем собрать проект. Должно получиться что-то подобное
Теперь пытаемся ввести некорректное значение в поле Width (например буквы) и в поле Quantity (буквы или дробное значение)
Теперь немного о том, как это работает:
Мы сделали обработчик нажатия клавиш для каждого поля ввода ManualValidationDialog::OnTextCtrlUpdate
и в этом обработчике изменяем картинку для соответствующего элементаwxStaticBitmap
.
Также мы сделали обработчик события wxEVT_UPDATE_UI
для кнопки Submit где делаем кнопку активной только в случае успешной проверки введенных значений во всех дочерних элементах управления.
Смотрим на окошки – красиво. Смотрим на исходник – не очень, т.к. опять много кода не связанного напрямую с функционалом приложения.
Сейчас мы попробуем немного автоматизировать отображенис коррекнтости ввода значения. Для этого создадим компонент, которые будет содержать в себе элемент управления и пиктограмму для отображения текущего статуса
wxInputBox.h
#ifndef _WX_INPUT_BOX_H #define _WX_INPUT_BOX_H #include <wx/wx.h> #include <wx/validate.h> #define wxInputBoxName wxT("wxInputBox") enum wxInputBoxBitmapType { wxINPUTBOX_OK, wxINPUTBOX_ERROR }; class wxInputBox : public wxControl { DECLARE_DYNAMIC_CLASS(wxInputBox); wxStaticBitmap * m_StatusBitmap; wxWindow * m_Editor; wxBitmap m_OKBitmap; wxBitmap m_ErrorBitmap; public: wxInputBox(); wxInputBox(wxWindow * parent, wxWindowID id, const wxPoint & pos = wxDefaultPosition, const wxSize & size = wxDefaultSize, long style = wxNO_BORDER, const wxValidator & validator = wxDefaultValidator, const wxString & name = wxInputBoxName); wxInputBox(wxWindow * parent, wxWindowID id, wxWindow * editor, const wxBitmap & ok_bitmap = wxNullBitmap, const wxBitmap & error_bitmap = wxNullBitmap, const wxPoint & pos = wxDefaultPosition, const wxSize & size = wxDefaultSize, long style = wxNO_BORDER, const wxValidator & validator = wxDefaultValidator, const wxString & name = wxInputBoxName); bool Create(wxWindow * parent, wxWindowID id = wxID_ANY, wxWindow * editor = NULL, const wxBitmap & ok_bitmap = wxNullBitmap, const wxBitmap & error_bitmap = wxNullBitmap, const wxPoint & pos = wxDefaultPosition, const wxSize & size = wxDefaultSize, long style = wxNO_BORDER, const wxValidator & validator = wxDefaultValidator, const wxString & name = wxInputBoxName); virtual bool Validate(); virtual void SetValidator(const wxValidator& validator); static wxBitmap GetDefaultStatusBitmap(wxInputBoxBitmapType bitmap_type); void SetEditor(wxWindow * editor); wxBitmap GetStatusBitmap(wxInputBoxBitmapType bitmap_type); void SetStatusBitmap(wxInputBoxBitmapType bitmap_type, const wxBitmap & bitmap); wxValidator * GetValidator() const; DECLARE_EVENT_TABLE() void OnValueChanged(wxCommandEvent & event); void OnSize(wxSizeEvent & event); }; #endif
wxInputBox.cpp
#include </wxInputBox/wxInputBox.h> #include </image.h> #include "inputbox_ok.xpm" #include "inputbox_error.xpm" IMPLEMENT_DYNAMIC_CLASS(wxInputBox, wxControl); BEGIN_EVENT_TABLE(wxInputBox, wxControl) EVT_SIZE(wxInputBox::OnSize) END_EVENT_TABLE() wxInputBox::wxInputBox() : wxControl(), m_Editor(NULL) { } wxInputBox::wxInputBox(wxWindow * parent, wxWindowID id, wxWindow * editor, const wxBitmap & ok_bitmap, const wxBitmap & error_bitmap, const wxPoint & pos, const wxSize & size, long style, const wxValidator & validator, const wxString & name) : m_Editor(NULL) { Create(parent, id, editor, ok_bitmap, error_bitmap, pos, size, style, validator, name); } wxInputBox::wxInputBox(wxWindow * parent, wxWindowID id, const wxPoint & pos, const wxSize & size, long style, const wxValidator & validator, const wxString & name) : m_Editor(NULL) { Create(parent, id, NULL, wxNullBitmap, wxNullBitmap, pos, size, style, wxDefaultValidator, name); } bool wxInputBox::Create(wxWindow * parent, wxWindowID id, wxWindow * editor, const wxBitmap & ok_bitmap, const wxBitmap & error_bitmap, const wxPoint & pos, const wxSize & size, long style, const wxValidator & validator, const wxString & name) { bool res = wxWindow::Create(parent, id, pos, size, style, name); if(res) { m_OKBitmap = ok_bitmap; m_ErrorBitmap = error_bitmap; if(!m_OKBitmap.Ok()) m_OKBitmap = wxInputBox::GetDefaultStatusBitmap(wxINPUTBOX_OK); if(!m_ErrorBitmap.Ok()) m_ErrorBitmap = wxInputBox::GetDefaultStatusBitmap(wxINPUTBOX_ERROR); wxBoxSizer * sizer = new wxBoxSizer(wxHORIZONTAL); SetSizer(sizer); if(editor) { SetEditor(editor); m_Editor->SetValidator(validator); } } return res; } void wxInputBox::SetEditor(wxWindow * editor) { if(GetSizer()) { GetSizer()->Clear(true); } wxBoxSizer * sizer = new wxBoxSizer(wxHORIZONTAL); SetSizer(sizer); wxBitmap bitmap; m_StatusBitmap = new wxStaticBitmap(this, wxID_ANY, bitmap); if(editor) { m_Editor = editor; m_Editor->Reparent(this); sizer->Add(m_Editor, 1, wxEXPAND|wxRIGHT, 5); if(m_Editor->IsKindOf(CLASSINFO(wxTextCtrl))) { m_Editor->Connect(m_Editor->GetId(), wxEVT_COMMAND_TEXT_UPDATED, (wxObjectEventFunction)&wxInputBox::OnValueChanged); } if(m_Editor->IsKindOf(CLASSINFO(wxListBox))) { m_Editor->Connect(m_Editor->GetId(), wxEVT_COMMAND_LISTBOX_SELECTED, (wxObjectEventFunction)&wxInputBox::OnValueChanged); } if(m_Editor->GetValidator()) { if(m_Editor->GetValidator()->TransferFromWindow()) { bitmap = GetStatusBitmap(wxINPUTBOX_OK); } else bitmap = GetStatusBitmap(wxINPUTBOX_ERROR); } else bitmap = GetStatusBitmap(wxINPUTBOX_OK); } else { bitmap = wxInputBox::GetStatusBitmap(wxINPUTBOX_ERROR); sizer->Add(100, -1, 1, wxEXPAND|wxRIGHT, 5); } sizer->Add(m_StatusBitmap, 0, wxALIGN_CENTER); sizer->Fit(this); SetBestSize(GetSize()); } wxBitmap wxInputBox::GetStatusBitmap(wxInputBoxBitmapType bitmap_type) { switch(bitmap_type) { case wxINPUTBOX_OK: if(m_OKBitmap.Ok()) { return m_OKBitmap; } case wxINPUTBOX_ERROR: if(m_ErrorBitmap.Ok()) { return m_ErrorBitmap; } default: break; } return wxInputBox::GetDefaultStatusBitmap(bitmap_type); } void wxInputBox::SetStatusBitmap(wxInputBoxBitmapType bitmap_type, const wxBitmap & bitmap) { switch(bitmap_type) { case wxINPUTBOX_OK: m_OKBitmap = bitmap; break; case wxINPUTBOX_ERROR: m_ErrorBitmap = bitmap; break; default: break; } } wxBitmap wxInputBox::GetDefaultStatusBitmap(wxInputBoxBitmapType bitmap_type) { if(!wxImage::FindHandler(wxBITMAP_TYPE_XPM)) { wxImage::AddHandler(new wxXPMHandler); } switch(bitmap_type) { case wxINPUTBOX_OK: return wxBitmap(inputbox_ok_xpm); case wxINPUTBOX_ERROR: return wxBitmap(inputbox_error_xpm); default: break; } return wxBitmap(16,16); } void wxInputBox::OnValueChanged(wxCommandEvent & event) { wxWindow * target = wxDynamicCast(event.GetEventObject(), wxWindow); if(target) { wxInputBox * parent = wxDynamicCast(target->GetParent(), wxInputBox); if(parent) { wxValidator * validator = target->GetValidator(); wxInputBoxBitmapType bitmap_type = wxINPUTBOX_ERROR; if(validator) { if(validator->Validate(this)) { bitmap_type = wxINPUTBOX_OK; } parent->m_StatusBitmap->SetBitmap( parent->GetStatusBitmap(bitmap_type)); } } } } void wxInputBox::OnSize(wxSizeEvent & event) { Layout(); } bool wxInputBox::Validate() { if(m_Editor) { wxValidator * validator = m_Editor->GetValidator(); if(validator) { return validator->Validate(this); } } return true; } void wxInputBox::SetValidator(const wxValidator& validator) { if(m_Editor) { m_Editor->SetValidator(validator); } }
Как видно из исходного кода, наш компонент производит замену обработчика события wxEVT_COMMAND_TEXT_UPDATED
(или wxEVT_COMMAND_LISTBOX_SELECTED
) в ассоциированном с ним элементе управления. Это значит, что если ваш элемент управления имеет функционально важный обработчик указанного выше типа, то применять такой подход нельзя. Вместо этого можно, например переопределить обработку события wxEVT_UPDATE_UI
для пиктограммы (и в нем выполнять проверку введенного значения) и этим решить проблему. Но такой подход более ресурсоемкий.
Внесем дополнительные изменения в класс окна
MainFrame.h
#ifndef _MAIN_FRAME_H #define _MAIN_FRAME_H ... class MainFrame : public wxFrame { public: ... DECLARE_EVENT_TABLE(); ... void OnShowExtendedValidators(wxCommandEvent & event); }; #endif
MainFrame.cpp
... #include "ExtendedValidatorsDialog.h" ... BEGIN_EVENT_TABLE(MainFrame, wxFrame) ... EVT_BUTTON(ID_EXTENDED_VALIDATORS_BUTTON, MainFrame::OnShowExtendedValidators) END_EVENT_TABLE() ... MainFrame::MainFrame() : wxFrame(NULL, wxID_ANY, _("wxFPValidator Test"), wxDefaultPosition, wxDefaultSize, wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU|wxRAISED_BORDER) { ... wxButton * extended_validators_button = new wxButton(this, ID_EXTENDED_VALIDATORS_BUTTON, _("Extended Validators - wxInputBox")); sizer->Add(extended_validators_button, 0, wxGROW|wxALL, 5); ... } ... void MainFrame::OnShowExtendedValidators(wxCommandEvent & event) { ExtendedValidatorsDialog * dlg = new ExtendedValidatorsDialog(this); if(dlg->ShowModal() == wxID_OK) { wxString result; result = wxString::Format( _("Name = %s\r\nAge = %i\r\nSalary = %1.2f\r\nMarital Status = %s\r\nSkills = "), dlg->GetName().GetData(), dlg->GetAge(), dlg->GetSalary(), dlg->GetMaritalStatus() ? wxT("married") : wxT("not married")); wxString skill_list; for(size_t i = 0; i < dlg->GetSkills().Count(); i++) { if(!skill_list.IsEmpty()) skill_list += wxT(","); skill_list += wxString::Format(wxT("%i"), dlg->GetSkills()[i]); } result += skill_list; wxMessageBox(result); } dlg->Destroy(); }
ExtendedValidatorsDialog.h
#ifndef _EXTENDED_VALIDATORS_DIALOG_H #define _EXTENDED_VALIDATORS_DIALOG_H #include </wx.h> #define ExtendedValidatorsDialogName _("Extended Validators") class ExtendedValidatorsDialog : public wxDialog { wxString m_Name; int m_Age; double m_Salary; bool m_Married; wxArrayInt m_Skills; void CreateControls(); public: ExtendedValidatorsDialog(); ExtendedValidatorsDialog(wxWindow * parent, wxWindowID id = wxID_ANY, const wxString title = ExtendedValidatorsDialogName); bool Create(wxWindow * parent, wxWindowID id = wxID_ANY, const wxString title = ExtendedValidatorsDialogName); wxString GetName() {return m_Name;} int GetAge() {return m_Age;} double GetSalary() {return m_Salary;} bool GetMaritalStatus() {return m_Married;} wxArrayInt & GetSkills() {return m_Skills;} DECLARE_EVENT_TABLE() void OnOKUpdateUI(wxUpdateUIEvent & event); }; #endif
ExtendedValidatorsDialog.cpp
#include "ExtendedValidatorsDialog.h" #include <wx/textctrl.h> #include <wx/spinctrl.h> #include <wx/checkbox.h> #include <wx/listbox.h> #include <wx/valgen.h> #include <wx/wxFPValidator/wxFPValidator.h> #include <wx/wxInputBox/wxInputBox.h> enum { ID_NAME_TEXTCTRL_EX = 11001, ID_AGE_SPINCTRL_EX, ID_SALARY_TEXTCTRL_EX, ID_MARITAL_CHECKBOX_EX, ID_SKILLS_LISTBOX_EX }; BEGIN_EVENT_TABLE(ExtendedValidatorsDialog, wxDialog) EVT_UPDATE_UI(wxID_OK, ExtendedValidatorsDialog::OnOKUpdateUI) END_EVENT_TABLE() ExtendedValidatorsDialog::ExtendedValidatorsDialog() { } ExtendedValidatorsDialog::ExtendedValidatorsDialog(wxWindow * parent, wxWindowID id, const wxString title) { Create(parent, id, title); } bool ExtendedValidatorsDialog::Create(wxWindow * parent, wxWindowID id, const wxString title) { bool res = wxDialog::Create(parent, id, title); if(res) { SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY); m_Name = wxEmptyString; m_Age = 20; m_Salary = 500; m_Married = false; m_Skills.Clear(); CreateControls(); Centre(); } return res; } void ExtendedValidatorsDialog::CreateControls() { wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL); SetSizer(sizer); wxFlexGridSizer * flexgrid_sizer = new wxFlexGridSizer(2,5); flexgrid_sizer->AddGrowableCol(1); wxStaticText * name_label = new wxStaticText(this, wxID_ANY, _("Name:")); wxStaticText * age_label = new wxStaticText(this, wxID_ANY, _("Age:")); wxStaticText * salary_label = new wxStaticText(this, wxID_ANY, _("Salary:")); wxStaticText * marital_label = new wxStaticText(this, wxID_ANY, _("Marital Status:")); wxStaticText * skills_label = new wxStaticText(this, wxID_ANY, _("Skills:")); wxTextCtrl * name_textctrl = new wxTextCtrl(this, ID_NAME_TEXTCTRL_EX, wxEmptyString, wxDefaultPosition, wxSize(150,-1)); wxTextCtrl * age_textctrl = new wxTextCtrl(this, ID_AGE_SPINCTRL_EX, wxEmptyString); wxTextCtrl * salary_textctrl = new wxTextCtrl(this, ID_SALARY_TEXTCTRL_EX, wxEmptyString); wxCheckBox * marital_checkbox = new wxCheckBox(this, ID_MARITAL_CHECKBOX_EX, wxT("Married")); wxArrayString skills; skills.Add(wxT("C/C++")); skills.Add(wxT("C#")); skills.Add(wxT("Delphi")); skills.Add(wxT("Visual Basic")); skills.Add(wxT("ASP.NET")); skills.Add(wxT("SQL")); wxListBox * skills_listbox = new wxListBox(this, ID_SKILLS_LISTBOX_EX, wxDefaultPosition, wxDefaultSize, skills, wxLB_MULTIPLE); name_textctrl->SetValidator(wxFPValidator(&m_Name)); age_textctrl->SetValidator(wxFPValidator(&m_Age)); salary_textctrl->SetValidator(wxFPValidator(&m_Salary)); marital_checkbox->SetValidator(wxGenericValidator(&m_Married)); skills_listbox->SetValidator(wxGenericValidator(&m_Skills)); wxInputBox * name_inputbox = new wxInputBox(this, wxID_ANY); wxInputBox * age_inputbox = new wxInputBox(this, wxID_ANY); wxInputBox * salary_inputbox = new wxInputBox(this, wxID_ANY); wxInputBox * skills_inputbox = new wxInputBox(this, wxID_ANY); name_inputbox->SetEditor(name_textctrl); age_inputbox->SetEditor(age_textctrl); salary_inputbox->SetEditor(salary_textctrl); skills_inputbox->SetEditor(skills_listbox); flexgrid_sizer->Add(name_label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(name_inputbox, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flexgrid_sizer->Add(age_label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(age_inputbox, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flexgrid_sizer->Add(salary_label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(salary_inputbox, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flexgrid_sizer->Add(marital_label, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(marital_checkbox, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); flexgrid_sizer->Add(skills_label, 0, wxALIGN_LEFT|wxALIGN_TOP|wxALL|wxADJUST_MINSIZE, 5); flexgrid_sizer->Add(skills_inputbox, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 5); sizer->Add(flexgrid_sizer, 0, wxEXPAND|wxALL, 10); wxBoxSizer * button_sizer = new wxBoxSizer(wxHORIZONTAL); wxButton * ok_button = new wxButton(this, wxID_OK, _("OK")); wxButton * cancel_button = new wxButton(this, wxID_CANCEL, _("Cancel")); button_sizer->Add(ok_button, 0, wxLEFT|wxBOTTOM, 5); button_sizer->Add(cancel_button, 0, wxLEFT|wxRIGHT|wxBOTTOM, 5); sizer->Add(button_sizer, 0, wxALIGN_CENTER|wxBOTTOM, 5); TransferDataToWindow(); sizer->Fit(this); } void ExtendedValidatorsDialog::OnOKUpdateUI(wxUpdateUIEvent & event) { event.Enable(Validate()); }
Как видно, из дополнительного кода остался только обработчик события wxEVT_UPDATE_UI
для кнопки. Все остальное реализует наш новій компонент wxInputBox
Подведем итоги
- Мы создали новый валидатор, позволяющий выполнять привязку переменных типа double и выполнять проверку корректности ввода значений
- Создали динамическую индикацию корректности ввода данных
- Разработали компонент, позволяющий автоматизировать работу, связанную с индикацией корректности ввода данных
- Продемонстрировали работу компонента на примере 🙂
К слову сказать, что попмимо стандартных валидаторов, существуют еще валидаторы от сторонних разработчиков: