Для разминки в с++ пишу небольшую ООП надстройку над WinApi, для того чтобы можно было парой строчек кода создавать окна и проводить необходимые основные манипуляции (такие как смена текста, размеров, установка событий на элементах управления и так далее). Планировалось что ее использование будет примерно таким:
WqWindow::WqBegin();
WqWindow w;
WqButton b(&w);
WqTextBox tb(&w);
tb.SetPosition(WqPosition(50, 10))
->SetSize(WqSize(200, 25))
->SetText("");
b.SetText("Cool button")
->SetSize(WqSize(150, 25))
->SetPosition(WqPosition(50, 50))
->SetAcnhor(WqControlAnchor(false, false, true, false))
->SetOnClick([&b, &tb, &w]() { cout << "Clicked!" << endl; });
w.SetTitle("Cool window")
->SetSize(WqSize(400, 400))
->ClosesProgram(false)
->SetOnClose([]() { cout << "Closed!" << endl; return true; })
->Show();
WqWindow::WqEnd();
Принцип примерно такой - при создании элемента управления указатель на него добавляется в вектор элементов объекта окна (WqWindow) а при удалении, в деструкторе, этот элемент из вектора убирается. Примерно вот таким образом
//Убираем из списка элементов управления окна
this->window_->controls_.erase(std::remove(this->window_->controls_.begin(), this->window_->controls_.end(), this), this->window_->controls_.end());
Сами события кнопок и прочих элементов обрабатываются в оконной процедуре. Принцип примерно следующий : получаем хендл окна вызвавшего событие (HWND), используя пользовательский указатель (который присвоили при создании WqWindow окна) получаем связанный объект WqWindow и обращаемся к вектору элементов управления. Проходим по ним в цикле, и вызываем лямбда-функции этих элементов. Выглядит это примерно так:
case WM_COMMAND:
if (wqWindow && !wqWindow->controls_.empty()) {
const std::vector<WqControl*> safeControlPointers(wqWindow->controls_);
for (WqControl * control : safeControlPointers)
{
if (control->initialized_)
{
if (HIWORD(wParam) == EN_CHANGE) {
if (control && control->ControlClassName() == "Edit" && control->GetHWND() == (HWND)(lParam)) {
WqTextBox * pTextBox = ((WqTextBox*)control);
if (pTextBox->onChanged_) {
pTextBox->onChanged_();
}
}
}
else {
if (control->ControlClassName() == "Button" && control->GetHWND() == (HWND)(lParam)) {
WqButton * pButton = ((WqButton*)control);
if (pButton->onClick_) {
pButton->onClick_();
}
}
}
}
}
}
break;
И тут я подумал - а что если пользователь данной библиотеки захочет в лямба выражении удалить (вызвав деструктор) какой-то элемент управления, например вот так:
->SetOnClick([&b, &tb, &w]() { tb.~WqTextBox(); });
На этот случай я как раз и добавил переменную safeControlPointers, чтобы при проходе по вектору мы работали как-бы с копией вектора, а не тем вектором размер которого будет меняться. Вроде должно было быть все в порядке, но все равно возникала ошибка при выполнении (при нажатии на кнопку). Дело в том что в копии вектора оставался указатель на объект который как-бы удален. НЕ ЗНАЮ КАК, но мне помогло следующее - я объявил в WqControl флаг control->initialized_, который в конструкторе становился true а деструкторе устанавливался в flase. В цикле вы наверное заметили проверку
if (control->initialized_)
Именно благодаря этому программа не ломалась при нажатии на кнопку. Я до конца так и не понял почему (ведь объект уничтожается, удаляется из памяти, соответственно доступ к каким либо его членам невозможен). Буду рад, если поясните как это возможно. Ну на этом я не остановился, и решил попробовать создать TextBox (который потом задумал удалить при клике) не в стеке а в куче (то есть при помощи оператора new) а затем вызвать delete при клике. Вот тут все окончательно сломалось. И не помогают никакие проверки (на пустоту указателя и прочие).
Вопрос : Как реализовать подобный механизм, чтобы при клике была возможность удалять другие элементы? Как правильно следует подходить к этому? За раннее спасибо.
Всем спасибо за советы, похоже проблема решена. Я просто избавился от цикла в обработке сообщения WM_COMMAND. У каждого элемента управления ведь есть пользовательский указатель на объект (содержащий все необходимые функции обратного вызова). Почему-то забыл о нем. Я просто получаю хендл эл-мента управления а затем из него достаю этот указатель, и вызываю нужные функции. Теперь это выглядит так:
case WM_COMMAND:
if (lParam) {
WqControl * wqControl = (WqControl*)GetWindowLongPtr((HWND)(lParam), GWLP_USERDATA);
if (wqControl) {
if(wqControl->ControlClassName() == "Edit"){
WqTextBox * pTextBox = ((WqTextBox*)wqControl);
if (pTextBox->onChanged_) {
pTextBox->onChanged_();
}
}
else if (wqControl->ControlClassName() == "Button") {
WqButton * pButton = ((WqButton*)wqControl);
if (pButton->onClick_) {
pButton->onClick_();
}
}
}
}
break;
В итоге цикл не запускается, и ошибок при удалении из вектора не возникает. Да и хранить список указателей действительно, получается, не обязательно. По крайней мере пока-что так кажется..
Современные инструменты для криптотрейдинга: как технологии помогают принимать решения
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости