Отправляет email-рассылки с помощью сервиса Sendsay

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

  Все выпуски  

Служба Рассылок Городского Кота


Служба Рассылок Городского Кота
    


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

    выпуск No.9 от 11/07/2000

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

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

Из входящей почты

Мы с вами уже разобрали ответы на вопрос о том, почему в 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, а она (я надеюсь) только начинает раскачиваться.

Спасибо за вашу работу и за ее результат.
-- 
Пишите письма...
(адрес может быть опубликован, но не продан спаммерам :)

- Чепкий Николай ( mailto:alterego@a-teleport.com)

Адрес я опубликовал, но спаммерам не продавал  - так что моя совесть на этот счет чиста. ;)  Если это сделает кто-нибудь из читателей - это будет на его, а не моей, совести.

Вопрос этот обсуждался в прошлом выпуске.  Преимущество способа, предложенного Николаем, заключается в автоматизации обработки нажатий клавиш. Так что вместо неуклюжего switch'a в случае большого количества клавиш мы получаем удобный списочек - и минимум кода. 

___________________

Один из читателей прислал интересный совет, предлагаю его вашему вниманию:

Привет!
Хочу обратить внимание на то, что изменение формы окон при помощи SetWindowRgn() не всегда правильно работает в старых версиях Windows -- в частности, такая ситуация наблюдалась под Windows 95 (PLUS) не OSR 2.

Зато совершенно точно это работает под '98, NT, 2000.
-------
Хочу предложить полезную уловку, позволяющую при использовании MFC-шаблонов документов управлять MDI-окнами из приложения. Этот трюк можно использовать при отображении разных категорий данных в различных окнах. При этом можно, в частности, автоматически переключать активные MDI-окна при обновлении данных в них.

Представьте библиотеку (класс), следующего вида:


class TReg
{
 public
   static CMapStringToPtr map;

   static BOOL RegisterTemplate( CString strName, CDocTemplate * ptr );
   static BOOL HasOpenViews( CString strName );
   static BOOL PostForAllViews( CString strName, UINT msg, WPARAM w, LPARAM p );
   static BOOL SendForAllViews( CString strName, UINT msg, WPARAM w, LPARAM p );

   static CDocTemplate * GetTemplate( CString strName );
. . . 
};

Зачем все элементы статические -- легко понять, ведь у нас только один 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);

  if( msg == NULL )
    pDoc->UpdateAllViews( NULL );
  else
    for ( POSITION p1= pDoc->GetFirstViewPosition (); p1 != NULL; )
    {
      CView * pView= pDoc->GetNextView (p1);
      pView->PostMessage (msg, w, l);
    }
}


- проверить, имеются ли открытые окна, относящиеся к данному шаблону:

for ( POSITION pos= pTempl->GetFirstDocPosition (); pos != NULL )
{
 CDocument * pDoc= pTempl->GetNextDoc (pos);

 for ( POSITION p1= pDoc->GetFirstViewPosition (); p1 != NULL; )
 {
  CView * pView= pDoc->GetNextView (p1);

  if( pView != NULL ) return TRUE;
 }
}
return FALSE;


и т.д.

Активизация (всплывание наверх) MDI-окна в программе проще всего реализуется добавлением примерно такого метода класса CView:


void CMyView::DoActivate( )
{
  CMDIChildWnd * pFrm = (CMDIChildWnd *)(GetParent( ));

  if( pFrm != NULL && IsWindow( pFrm->m_hWnd ) )
    pFrm->MDIActivate( );
}

- Victor Yakovlev

Да, это может быть полезно, особенно для тех, кто сталкивался (или кому еще только предстоит столкнуться) с разработкой сложных 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);

  times.DeleteObject();

  return TRUE; 
}

m_Static - переменная, представляющая соответствующий Static-контрол. Вместо нее можно воспользоваться указателем, возвращаемым ф-цией GetDlgItem( ).  Как вы видите, объект CFont уничтожается сразу же после вызова SetFont( ).

А вот как надо было сделать:

class CAboutDlg : public CDialog
{
 ....
 private:
  CStatic m_Static;
  CFont m_fntTahoma;  // добавляем шрифт в диалог
}

BOOL CAboutDlg::OnInitDialog() 
{

  CDialog::OnInitDialog();

  m_fntTahoma.CreatePointFont (100,"Tahoma");
  m_Static.SetFont(&m_fntTahoma);

  return TRUE; 
}

BOOL CAboutDlg::DestroyWindow() 
{
  m_fntTahoma.DeleteObject();

  return CDialog::DestroyWindow();
}

Здесь все работает как надо. Вскоре, когда надоела 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.

- Igor Sorokin

/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /

За сим откланиваюсь. 
Будьте здоровы
!

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

[ Предыдущие выпуски | Статистика рассылки ]

    
     


http://subscribe.ru/
E-mail: ask@subscribe.ru

В избранное