Программирование на Visual C++

Выпуск №13 от 7 сентября 2000 г.

Здравствуйте, дорогие подписчики!

Чрезвычайно рад приветсвовать вас после чуть более чем месячного отдыха. Как вы уже наверное сами догадались, рассылка вышла из отпуска и снова будет периодически радовать вас всяческой полезной информацией о мощнейшем современном инструменте разработки, а именно Microsoft Visual C++, ну а также о всем, что с ним связано. Начинается новый сезон выпусков, и начинается он с весьма счастливого номера – тринадцатого ;) Впрочем, я никогда не был особенно суеверным, так что пропускать это замечательное число никак не намерен.

Я взял на себя смелость немного изменить оформление рассылки – спасибо ребятам с subscribe.ru, они наконец-то догадались, что далеко не все пребывают в диком восторге от их цвета для фона страницы HTML-варианта, который мне почему-то всегда напоминал цвет известного сельскохозяйственного удобрения. Теперь это можно изменить, что я, особо не мешкая, и сделал.  Впрочем, как говорится, на вкус и цвет товарища нет, так что будем условно считать возможную дискуссию на эту тему несостоявшейся.

За время моего отсутствия пришло много писем с вопросами и предложениями, и я думаю, что самые интересные из них мы разберем в ближайших выпусках.

Есть, к сожалению, и не очень приятные новости. Сейчас у меня будет оставаться не так много времени на рассылку, как раньше. Выпуски будут выходить так же регулярно, но, возможно, самую малость пореже. И еще: я не буду делать отдельную текстовую версию, придется положиться на конвертер сервера. Прошу прощения у подписчиков текстового варианта. Понимаю, что звучит банально (я такие утверждения встречал почти в каждой рассылке, а теперь дошел до того, что пишу это сам) но HTML-вариант ДЕЙСТВИТЕЛЬНО гораздо лучше выглядит и занимает ненамного больше времени для загрузки.

MFC

Начать всегда труднее всего, так что, облегчая себе задачу, я решил не начинать все заново и строить "новый мир" на обломках "старого", а просто продолжить с того места, на котором остановился. Итак, если вы помните, в предыдущем (двенадцатом) выпуске, я вам рассказывал о том, как с помощью MFC организовать набор закладок, без которых, замечу, просто немыслим удобный и структурированный интерфейс для диалога с большим количеством различных опций (вновьподписавшимся рекомендую обратиться к архиву рассылки, ссылка на него есть в конце этого выпуска). Кстати, в некоторых книгах закладки называют "вкладками", и некоторые считают, что так правильнее, хотя суть от этого, конечно же, не меняется.

Набор закладок: пара замечаний

Описанный мной в #12 способ организации закладок в принципе работает, но имеет два существенных недостатка, на которые обратил внимание мой хороший друг, программист Bad Sector. Одновременно он подсказал пути их устранения.

Недостаток первый: в фокусе ввода закладки, при нажатии  клавиши "Enter" не происходит передача сообщения родительскому окну, содержащему набор, а идет его обработка "на месте". Это может, в худшем случае, привести к удалению содержимого текущей закладки с экрана, а кнопка по умолчанию родительского диалога не сработает. Сразу скажу, что проблема разрешается перекрыванием PreTranslateMessage(), но есть способ проще и лучше, который я опишу чуть ниже.

Второй недостаток гораздо серьезнее: из-за того, что объекты-страницы закладок у меня создаются и удаляются динамически  во время переключения, состояние их элементов не сохраняется. Это не имеет значения, если у вас там одни кнопки ;) но чаще всего бывает как раз наоборот.

Помните, я вам обещал рассказать про классы CPropertySheet и CPropertyPage? Время первого еще не пришло, а вторым мы сейчас как раз и займемся. Потому как использование его вместо CDialog в качестве родительского класса для наших страниц-закладок МОМЕНТАЛЬНО снимает первую проблему. Сообщение будет передаваться куда надо и как надо. Отметьте, что CProperyPage сама наследует от CDialog, и своим поведением отличается от него  в таких вот ситуациях. А еще обычно как-то упускается из виду, что CPropertyPage можно использовать отдельно, а не в связке с СPropertySheet. 

Таким образом, первый недостаток устранен, перейдем ко второму. Здесь тоже все несложно: необходимо выбрать – либо вы будете хранить настройки каждой из закладок в содержащем их диалоге, и при переключении каждый раз их сохранять/загружать (путь мазохиста). Либо же вы все нужные закладки создадите заранее (в массиве, например), при открытии содержащего их диалога. При его же закрытии, вы, если это необходимо, все нужные данные из классов закладок можете скопировать в отдельные переменные, а затем со спокойной совестью удалить весь набор. В таком случае переключение закладок будет выглядеть следующим образом:

CPropertyPage *m_Pages[];

CTabCtrl m_Tabs;

int m_iLastPage=-1;

...

void CMyDlg::OnSelChangeTab(NMHDR* pNMHDR, LRESULT* pResult) {

 if (m_LastPage != -1) m_Pages[m_iLastPage]->ShowWindow(SW_HIDE);

 int c = m_Tabs.GetCurSel();

 m_Pages[c]->ShowWindow(SW_SHOW);

 m_Pages[c]->UpdateWindow();

 m_iLastPage = c;

 *pResult = 0;

}

ОБРАТНАЯ СВЯЗЬ

Еще письмо на тему отказа работать release-версии программы. И прошу не ворчать, потому что когда сами с такой проблемой встретитесь (скорее всего, когда программу показывать нужно уже через несколько дней), скажете этому человеку спасибо! 

Сам недавно столкнулся с "проблемой Release билда". Естественно, 95% всех подобных багов – это баги с памятью. Проблемы с NULL указателями ещё менее-более отслеживаются, а вот с "перетиранием" памяти – сложнее. 

Я решил эту проблему с помощью _CrtCheckMemory(). Вообще, в Windows есть минимальный набор механизмов для отладки, они все описаны в MSDN 'Debug Routines'. 

Т.е. сначала определяем с помощью внутреннего логгинга примерное место вылета проги – это нужно делать не в Debug build (там-то всё работает :), а в Release. Простой лог можно за полчаса набросать. Если прога не использует DirectX или OGL, то можно (наверное), использовать даже MessageBox. Главное – локализовать место появление бага. Надо сразу заметить, что, как правило, место, где прога вылетает, совсем не то же самое место, где есть баг. В моём случае баг был в инициализации, а валилась функция уже во время работы. Потом работаем с помощью assert(_CrtCheckMemory()); уже в Debug build, где есть дебаггер и всё такое. 

Если программа менее-более линейна и не сильно здоровая, то можно вставить assert( _CrtCheckMemory()); прямо по ходу выполнения, перед и после каждого подозрительного вызова. Он сработает, как только обнаружит поврежденный heap – мы сразу можем видеть где это происходит, и делать выводы. Надеюсь это хоть кому то поможет.

(Иван Невраев)

Ну вот и все на сегодня. До новых встреч и будьте здоровы!


(©Алекс Jenter mailto:jenter@mail.ru) (Красноярск, 2000.)







Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх