Мы с вами уже разобрали
ответы на вопрос о том, почему в Debug-версии
все иногда работает нормально, а в Release
появляются большие проблемы (этот вопрос
был задан в выпуске No.5). Уже после того, как
вышел выпуск с ответами на этот вопрос,
пришли еще несколько писем на эту тему.
Большинство сожалеет о том, что такой "элементарный"
нюанс - а именно, чреватость использования
макроса ASSERT, - остался вне обсуждения.
Для тех, кто не понял, в чем здесь дело:
макрос ASSERT(<условие>), в отличие от
сходного макроса VERIFY(<усл> ),
работает только в Debug-версии, а в Release-версии
этот макрос просто заменяется пустой
строкой, следовательно условие, которое
указывается в скобках, не проверяется.
Таким образом, если ваша программа
нашпигована такими вот макросами, и вы
компилируете ее как Release, проверка всех
условий совершенно незаметно для вас
исчезает.
А теперь у меня вопрос к авторам таких
ответов : Каким образом в Debug-версии все
может быть нормально, если исчезновение
ASSERT'ов оказалось критичным для работы Release-версии?
(Хотя, если честно, один такой способ
существует, и именно его, скорее всего.
имели ввиду авторы писем. Но я просто
никогда еще не встречал таких
оригиналов, которые в условие макроса
ASSERT умудрятся впихнуть что-нибудь помимо
самого условия, выделение памяти или
инициализацию объекта, например. Никогда
так не делайте! Впрочем, уверен, что
большинство до такого все-таки не
додумалось ;)
Итак, выходит в Debug-версии программа должна
была вылетать на "Assertion failed", а это вряд
ли можно назвать "нормальным выполнением".
Напоминаю, в самом вопросе утверждалось,
что в Debug программа работает без проблем.
Вообще, макрос ASSERT предназначен как раз для
того, чтобы именно Debug-версия и не работала
, если у вас что-то в программе не в порядке!
Таким образом, программист сможет сразу
понять, что и где у него не так (это, конечно,
в идеале ;).
Но замечу, что сам по себе нюанс этот
достаточно интересный. Итак, люди - обратите
внимание на макросы ASSERT и VERIFY! Напоминаю:
VERIFY, в отличие от ASSERT, сохраняется и в Release-версии,
хотя в последнем случае он не прерывает
программу даже если условие не выполняется.
Читателей, поднявшим этот вопрос, благодарю,
а это: Alexander Dymerets, Alexey "Locky"
B.R. и Serge Zakharchuk.
В отличие от большинства, Olga
Zamkovaya предложила другой способ
выяснить, в чем дело:
...К вопросу о недопустимой операции в Release версии программы
из выпуска
#5: в числе полезных советов "проверьте свой код",
"build all может помочь" и т.п. не было предложено воспользоваться опцией компилятора
/GZ (catch Release-build errors in Debug build), что, мне кажется, может быть полезно
в данной ситуации.)
- Olga
Zamkovaya
Что ж, думаю, и это
кому-то поможет - ловить Release-ошибки в Debug. По
крайней мере можно будет обнаруживать
ошибки на стадии, которая как раз
предназначена для отлова ошибок;) Thank you, Olga.
___________________
Пришло еще одно письмо на
тему обработки событий клавиатуры в
диалогах. В качестве дополнения в нем
описывается один трюк, который, возможно,
будет полезен и вам:
Здравствуйте, Алекс!
Решил попробовать свои силы во внесении посильного вклада в понимание
не самых понятных вещей, которые касаются каким-либо образом MS
VCPP.
Итак, в выпуске #5 промелькнул вопрос об обработке клавиш в диалоге. Я
в свое время столкнулся с точно таким же вопросом и даже собирался его
решать способом, которым решил автор вопроса, но меня не хватило: я
ленивый. Я нашел очень полезную вещь: использование акселераторов
(горячих клавиш) - accelerators - в диалогах. Пользуюсь этим способом
регулярно и до сих пор. Идея, в принципе, та же: перегрузить
PreTranslateMessage.
Код для этой функции:
BOOL CSomeDialog::PreTranslateMessage(MSG*
pMsg)
{
if (pMsg->message >= WM_KEYFIRST &&
pMsg->message <= WM_KEYLAST)
if (m_hAccel)
if (::TranslateAccelerator(m_hWnd,
m_hAccel, pMsg))
return TRUE;
return CDialog::PreTranslateMessage(pMsg);
}
Здесь m_hAccel - переменная-член
класса CSomeDialog типа
HACCEL, инициализированная в OnInitDialog таким, например, способом:
m_hAccel = ::LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(m_lpszTemplateName));
Если ее инициализировать таким образом, то будет произведена попытка
найти ресурс акселератора с тем же ID, что и ID диалога (например,
IDD_SOMEDIALOG), в котором можно прописать какие только душа пожелает
клавиши и их комбинации. Если же ресурс найден не будет, то ничего
страшного не произойдет.
Обрабатывать команды от акселератора можно стандартным способом -
ON_COMMAND в MESSAGE_MAP'е. Я их прописываю руками, без
ClassWizard'а. Да, кстати, можно запросто лепить в таблицу акселератора IDшки
кнопок
(push buttons). Хэндлер для обработки кнопки, объявленный с помощью
ON_BN_CLICKED, будет вызван автоматически (это связано с тем, что
ON_COMMAND и ON_BN_CLICKED на самом деле - одно и то же).
[. . . ]
Спасибо, что дочитали даже до этого места, надеюсь, содержанием не
разочаровал. Ваша рассылка уже rules, а она (я надеюсь) только
начинает раскачиваться.
Спасибо за вашу работу и за ее результат.
--
Пишите письма...
(адрес может быть опубликован, но не продан спаммерам :)
Адрес я
опубликовал, но спаммерам не продавал -
так что моя совесть на этот счет чиста. ;)
Если это сделает кто-нибудь из читателей -
это будет на его, а не моей, совести.
Вопрос этот
обсуждался в прошлом выпуске.
Преимущество способа, предложенного
Николаем, заключается в автоматизации
обработки нажатий клавиш. Так что вместо
неуклюжего switch'a в случае большого
количества клавиш мы получаем удобный
списочек - и минимум кода.
___________________
Один из читателей прислал
интересный совет, предлагаю его вашему
вниманию:
Привет!
Хочу обратить внимание на то, что изменение формы окон
при помощи SetWindowRgn() не всегда правильно работает
в старых версиях Windows -- в частности, такая ситуация наблюдалась под Windows 95
(PLUS) не OSR 2.
Зато совершенно точно это работает под '98, NT, 2000.
-------
Хочу предложить полезную уловку, позволяющую при использовании
MFC-шаблонов документов управлять MDI-окнами из приложения. Этот
трюк можно использовать при отображении разных категорий данных
в различных окнах. При этом можно, в частности, автоматически переключать
активные MDI-окна при обновлении данных в них.
Зачем все элементы статические -- легко
понять, ведь у нас только один MDI-фрейм.
Далее, в методе WinApp::InitInstance() при порождении шаблонов документов
вместо (или вместе с) AddDocTemplate( CDocTemplate * ) записываем TReg::RegisterTemplate(
"MyName", CDocTemplate * );
Здесь мы просто добавляем указатели шаблонов в словарь map.
С помощью метода GetTemplate() мы можем извлечь указатель на шаблон из словаря
по имени. Используя этот указатель, мы можем:
- открыть новое окно при помощи DocTemplate::OpenDocumentFile();
- закрыть все окна, относящиеся к данному шаблону;
- отправить сообщение всем окнам данного шаблона:
for ( POSITION pos= pTempl->GetFirstDocPosition
(); pos !=
NULL )
{
CDocument * pDoc= pTempl->GetNextDoc (pos);
Да, это может быть
полезно, особенно для тех, кто сталкивался (или
кому еще только предстоит столкнуться) с
разработкой сложных MDI-приложений - они
знают, как трудно добиться правильной
совместной работы всех дочерних окон.
Q.
Нужно изменить шрифт
у одного
элемента типа CStatic. Делаю это функцией
SetFont(CFont font). Фонт меняется у элемента ... и у всего окна :(. Включая
кнопки и другие элементы типа
static. Мне его надо было толстым сделать, так у меня такие кнопки стали -загляденье:))
Кто-нибудь знает в чем дело и как решить ?
-
LiMar
A. Присланные
ответы на этот вопрос сводились к двум
следующим: 1) сделать класс-наследник от CStatic и
перекрыть функцию прорисовки - OnPaint( ); 2) вызывать метод SetFont( ) именно
объекта CStatic (или указателя на этот контрол),
а не всего диалога.
Порекомендовавшие первый способ явно
забыли правило "бритвы" Оккама: не
множить сущностей без нужды. (Кстати, нам,
программистам, это правило особенно
полезно.) Если для того, чтобы поменять
шрифт, нужно создавать новый класс, ну уж
извините... Этим способом, конечно, можно
пользоваться, но я думаю, только в тех
случаях, когда без этого не обойтись.
Итак, второй ответ был больше всего похож на
искомую истину. Но "похож" - это еще не
значит "есть", так что я решил
проверить. Сделал простое SDI-приложение, и
попробовал в окне About у одной из надписей
поменять шрифт.
Как же я был рад, когда он в самом деле
изменился!!!
...Правда, на совершенно не тот, который я
хотел. Да и размерчик прежний остался... Это
было весело - в любом случае он ставил шрифт
System, хотя (у меня много шрифтов!) я
прописывал разные. Никакого результата. Способ
No.2 у меня не работал. Либо он был
неправильный, либо, как впоследствии
оказалось, правильный не до конца.
Через некоторое время мне это надоело, и я
решил, что раз уж не оказалось пророков
среди читателей, пророком придется стать
самому (это метафора;)
Самое обидное то, что ответ даже не
пришлось искать ! Он лежал на самом видном
месте в MSDN. Я ввел "SetFont" в строке поиска
и мгновенно обнаружил интереснейшую статью
с говорящим само за себя названием - "Correct
Use of the SetFont( ) Function in MFC".
Суть статьи сводилась к следующему:
Обычно в SetFont передают указатель на шрифт -
объект CFont. Так вот, обязательно нужно
проследить, чтобы этот объект не
уничтожился раньше, чем тот контрол, для
которого он создается!
Итак, как было у меня раньше (или "способ
No.2"):
BOOL CAboutDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CFont times;
times.CreatePointFont (100,"Times New Roman");
m_Static.SetFont(×);
times.DeleteObject();
return TRUE;
}
m_Static - переменная,
представляющая соответствующий Static-контрол.
Вместо нее можно воспользоваться
указателем, возвращаемым ф-цией GetDlgItem( ).
Как вы видите, объект CFont уничтожается сразу
же после вызова SetFont( ).
А вот как надо было сделать:
class
CAboutDlg : public CDialog
{
....
private:
CStatic m_Static;
CFont m_fntTahoma; // добавляем
шрифт в диалог
}
Здесь все работает
как надо. Вскоре, когда надоела Tahoma, я уже
наслаждался отлично выглядевшей
готической надписью. (Кстати, тут возникает
еще вопрос - получается, чтобы нужный шрифт
был всегда доступен, нужно распространять
его вместе с приложением? Конечно, это не
относится к стандартным Windows-шрифтам, типа
Arial, Times, Tahoma или Courier. Лучше все-таки
обходиться ими, когда возможно).
Тех, кто хочет получить больший контроль
над шрифтом - сделать его жирным, курсивом и
т.д. , отправляю прямиком к той же статье, да
еще к функции СFont::CreateFontIndirect( ).
Я прошу прощения, что,
возможно, слишком подробно расписал ответ
на этот вопрос (хотя не исключаю, что кому-то
это было интересно прочитать). Я
преследовал еще одну цель - сказать всем:
"Люди, учитесь пользоваться MSDN! На многие
ваши вопросы там уже отвечено!"
Ответ на этот вопрос прислали:
Николай Чепкий , Igor Sorokin, Alexander Dymerets, Pavel Vasev.
Q.Как получить доступ
к ресурсам DLL в самой DLL? Задача сводилась к
следующему - нужно было сделать диалоговое окно в функции, которая
находилась в DLL.
__declspec(dllexport) int MyDllFunction()
{
CDialog dlg ;
int ret = dlg.DoModal() ;
return ret ;
}
DLL имела ресурс Dialog для этого диалогового окна, но работать напрочь
отказывалась - этот ресурс не обнаруживался и окно не создавалось. DLL
собиралась как со статически линкуемой библиотекой MFC, так и с динамически
линкуемой библиотекой MFC.