/ *


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

 выпуск No. 4  от 25/06/2000


* /

Добрый день, уважаемые подписчики!

Мне приходит очень много ваших писем с вопросами, советами, комментариями, предложениями и т.д. Как повелось, самые интересные из них я публикую в рубриках "Обратная связь" и "Вопрос-ответ". Но, как вы наверное сами понимаете, я просто физически не в состоянии отвечать на такое количество вопросов, касающихся программирования. Я отвечаю быстро, когда сразу знаю ответ, но ведь чаще приходится самому сидеть и разбираться, а на это уходит много времени. От этого в первую очередь страдают другие рубрики рассылки - я не успеваю подготовить хороший материал для вас (кстати, я как раз хочу сделать несколько новых рубрик... но пусть лучше это будет сюрприз). Моя задача как автора рассылки состоит в том, чтобы готовить и публиковать интересную информацию по обозначенной тематике. Я же в последнее время больше сам занимаюсь  программированием. Для меня, это, конечно, полезно ;) но вот как насчет вас? На многие вопросы я уже ответил, хотя они не появятся в рассылке, т.к. довольно узко направлены и вряд ли интересны для "широкой общественности". Ответы на другие или слишком обширны и тянут на тему отдельного выпуска, или попросту элементарны.

Решив, что так дальше дело все-таки не пойдет, я придумал новую схему. Мне пришло несколько писем с предложениями помощи от программистов, выписывающих рассылку (огромное спасибо им!), так что теперь лишь на некоторые (самые интересные ;) вопросы я буду отвечать лично (в рубрике "Вопрос-ответ"), а другие просто опубликую отдельно.  В дальнейшем эти вопросы (вкратце) уже вместе с ответами на них появятся в рубрике "Вопрос-ответ" (мне, кстати, предлагали расширить эту рубрику - вот хороший повод). Я надеюсь, наше с вами мнение совпадет и вы тоже посчитаете, что так будет лучше. Еще я очень полагаюсь на ваше сотрудничество - если знаете ответ на вопрос, не поленитесь и напишите! Человек будет вам благодарен, да и не только он, а все читатели, которые узнают что-то новое. А я лично преобладающую свою роль программиста-консультанта сменю на роль ведущего рассылки.

С другой стороны, я ни в коем случае не хочу рассылку превращать в тривиальную дискуссионную группу. Будет несколько новых рубрик! Поэтому количество вопросов, рассматриваемых в выпуске, будет  ограничиваться (в среднем) двумя-тремя вопросами в рубрике "Вопрос-ответ" плюс два-три вопроса, ожидающие ответа. 

Кстати, могу дать очень хороший совет для тех, кто отчаялся найти решение своей проблемы: попробуйте, действительно, задать вопрос в дискуссионной группе, например на news.microsoft.com, в microsoft.public.ru.vc или microsoft.public.russian.programming, а если знаете английский, лучше в одну из многочисленных microsoft.public.vc.* Там отвечают действительно быстро, сам не раз прибегал к этому! И не забудьте порекомендовать подписаться на рассылку :)

Очень многие спрашивают, где можно прочитать предыдущие выпуски рассылки. Отвечу лучше здесь: смотрите на http://subscribe.ru/archive/comp.prog.visualc/

Да, и еще. Выпуски, скорее всего, станут выходить чаще, до 3-4 раз в неделю, иначе размер каждого заметно увеличится. А я сам знаю, как неприятно забирать 30-40 Кб письма, когда постоянно рвется связь (да простят меня читатели с хорошей связью;)  В каждом выпуске помимо обсуждений и вопросов я постараюсь публиковать что-то  интересное. Have fun.

 

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

По поводу вопроса смены курсора в MFC-приложении (выпуск No.3) пришло несколько дополнений и замечаний (некоторые несущественные моменты я опустил, вместо них поставил знак "[...]", свои комментарии в теле письма я также привожу в квадратных скобках):

Подобно третьему случаю, но не только в PreCreateWindow()

В тот момент, когда хотим поменять курсор (для всего приложения) проделываем следующие действия:

::SetClassLong( theApp.m_pMainWnd->m_hWnd,
                GCL_HCURSOR,
              ::LoadCursor(theApp.m_hInstance,
               MAKEINTRESOURCE(IDC_MY_CURSOR)));

Немного длинная строчка кода :)) [...]
Как обычно встречается такая проблема: theApp, хоть и объявлена глобально (объявляется если проект создается стандартным Wizard-ом), не видна в других файлах проекта. Чтобы обойти это просто в файле объявления класса вашего приложения (производного от
CWinApp) сразу после объявления класса добавьте следующую строчку:

extern CMyApp theApp;

где CMyApp - имя класса вашего приложения.

- Andrew Gromyko

Замечу, что theApp так объявлять необязательно, так как многие, и я в том числе, пользуются вместо этого стандартными функциями MFC AfxGetApp() и AfxGetMainWnd(), которые возвращают указатель на объект-приложение и главное окно соответственно. 
Вот еще одно письмо на эту тему:

Проблема смены курсора меня настигла еще на раннем этапе моего программистского становления. Поэтому хочу предложить тот вариант, который является по-моему наиболее адекватно подходящим с точки зрения скорости и идеологии Win32 API. Способ три конечно хорош и его можно расширить на все время существования окна. Вот как происходит вызов в классе окна:

::SetClassLong (m_hWnd,GCL_HCURSOR,
     (long)AfxGetApp()->LoadCursor (IDC_MY_CURSOR));

При этом курсор меняется перманентно, т.е. например при вызове SetCursor() вид курсора меняется при начале движения мыши. А так меняется просто класс окна. Работает быстро и надежно. [...]

- Alex (seaside@i.com.ua)

Спасибо Андрею и Алексу за это дополнение, теперь мы с вами знаем четыре способа изменить курсор;).  Должен признаться, в предыдущем выпуске я допустил оплошность, ляпнув что первый параметр у AfxRegisterWndClass() можно заменить на cs.style, причем, как назло, понял свою ошибку как раз в тот момент, когда отправлял рассылку "в эфир". Мне не преминули на эту ошибку указать, и я очень рад этому. Пожалуйста, будьте бдительны!!! Я тоже человек! Подробности читайте ниже...

Спасибо Вам за создание действительно нужной рассылки. [Мерси за комплимент ;) - AJ]
Я не так давно начал использовать MFC, и информация, ответы на вопросы мне очень пригодятся. Интересно, что уже в первом прочитанном мной выпуске обсуждался вопрос о курсоре, решение которого я буквально только что искал сам. Поэтому я решил написать этот отзыв в виде нескольких замечаний. На всякий случай оговорю, что все ниже написанное не более чем мое humble opinion :-))

1) Мне не кажется, что описанный Вами универсальный способ изменения курсора нерационален. Не этот обработчик, так
CWnd::OnSetCursor() все равно вызывается при каждом движении мыши. Поэтому разнице в скорости практически неоткуда взяться. Хотя, LoadCursor() действительно лучше вызвать один раз (когда этот курсор впервые устанавливается), сохранить дескриптор нового курсора, который и передавать системной функции SetCursor() в обработчике. Мне кажется, это будет по сути то же самое, что делается при обработке события WM_SETCURSOR по умолчанию.

2) О "песочных часах". Если операция, на время которой высвечиваются часики, относительно невелика по времени, и при ее выполнении приложение может не реагировать на другие события, проще всего выделить блок обработки и определить в этом блоке локальный объект
CWaitCursor. Все необходимые операции по установке и удалению курсора будут сделаны конструктором и деструктором этого объекта. Именно так, например, в MFC реализованы часики при открытии и сохранении документа. Если же во время операции система реагирует на другие события, то удобнее применять Begin/Restore/EndWaitCursor() [Итак, способов уже пять! - AJ]

3) Вы показываете пример с переопределением PreCreateWindow(), в котором регистрируется новый класс окна, и в конце пишете: "В качестве первого параметра для AfxRegisterWndClass() можно указать "cs.style", чтобы установить стиль окна по умолчанию." По-моему, это некорректно. Ведь cs.style есть комбинация стилей для окна, а не для класса окна, что требуется при регистрации класса.
[Виновен, Ваша честь! - AJ]  Мне пришлось столкнуться с этой проблемой, когда я хотел для своего класса CMyView добавить стиль CS_OWNDC, не меняя ничего больше. Дело в том, что при самом первом вызове CMyView::PreCreateWindow() класс окна просмотра еще не существует, так как он регистрируется в CView::PreCreateWindow(). Поэтому пришлось вызывать родительскую функцию дважды. Вот мое решение, которое, быть может, будет кому-то полезно: 

//h-файл

class CMyView :public CView
{
  public:
   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
  protected:
   static LPCTSTR lpszViewClassName;
}

//cpp-файл

LPCTSTR CMyView::lpszViewClassName = NULL;

BOOL CMyView::PreCreateWindow(CREATESTRUCT& cs)
{
 if (lpszViewClassName == NULL)
 {
  CView::PreCreateWindow(cs);

  WNDCLASS wc;
  //пытаемся получить информацию о классе
  //если не удается, выходим с ошибкой
  if (!::GetClassInfo(AfxGetInstanceHandle(),
                cs.lpszClass, &wc))
      return FALSE;

  // теперь изменяем в wc все, что нужно
  // например, стиль класса
  wc.style |= CS_OWNDC;
  // регистрируем класс
  lpszViewClassName = AfxRegisterWndClass(
          wc.style, wc.hCursor,
          wc.hbrBackground, wc.hIcon);
 }
 // изменяем класс окна на созданный нами:
 cs.lpszClass = lpszViewClassName;

 return CView::PreCreateWindow(cs);
}

[Я немножко подправил код и добавил комментарии. Надеюсь, автор на меня не обидится - AJ]
Есть у меня и вопросы, ответы на которые, возможно, будут интересны не только мне.

Q1) В приложении есть операция, которая требует, скажем, больше пяти минут времени, причем по некоторым причинам дальнейшее выполнение не может быть продолжено до завершения этой операции. Хотелось бы, чтобы при этом окно приложения нормально обновлялось, могло быть свернуто-развернуто и т.п. Я нашел некоторое решение, но оно требует создания второго цикла обработки сообщений и потому мне не очень нравится, хотелось бы сделать более естественно.

Q2) Можно ли переопределенный обработчик событий сделать подставляемым (inline)?

- Куканов Алексей (ask_atos@mail.ru)

Очень хорошее, обстоятельное письмо. Хотя оно и содержит вопросы, я все же решил поместить его в "обратную связь", т.к. по большей части относится к теме, ну а разбивать письмо на две части - это было бы варварство. Если кто-нибудь знает ответы на заданные вопросы - очень прошу поделиться! Вопросы действительно интересные.

Если у вас есть еще какие-либо комментарии на тему смены курсора мыши, пишите лучше сейчас. После следующего выпуска тема будет считаться закрытой.

 

/ / / / ВОПРОС - ОТВЕТ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / 

Вопросы прислал Ivan Pouzyrevsky. И хотя у некоторых из вас они могут вызвать улыбку, я решил опубликовать их в рассылке вместо личного ответа, т.к. все-таки довольно большое число начинающих программистов на VC далеко не сразу понимает, как работать с диалогами в концепции MFC. 

Q. Создаю кнопку About. В MFC Class Wizard создаю функцию: IDS_ABOUT->BN_CLICKED. А какой код на открытие окна About?

A.  Чтобы вызвать модальный (т.е. не разрешающий переключаться куда-то еще в приложении, пока  пользователь не закрыл его) диалог, следует воспользоваться ф-цией DoModal(): 

      CAboutDlg aboutDlg;
      aboutDlg.DoModal();

AppWizard генерирует такой код сам на команду ID_APP_ABOUT. Так что проще всего, если вы при создании приложения попросили AppWizard создать окно About, назначить Вашей кнопке идентификатор ID_APP_ABOUT. Тогда больше ничего делать не надо.
А вообще, таким образом можно вызвать не только диалоговое окно About, но и любое другое. Кому не совсем ясно, как обеспечить обмен данными с диалогом, присылайте заявки. В случае наличия интереса это станет одной из следующих тем выпуска. 
Господа опытные программисты, прошу не тратить силы на ворчание - я не могу угодить всем! Для вас тоже будет кое-что интересное.

Q. У меня в программе при написании слова выполняется функция. Например:

   if(UpperValue==CALCULATOR)
   {
     system("calc.exe");
     m_TestEdit="";
     UpdateData(FALSE);
   }

Но тут вопрос: как пользователю без изменения кода добавлять в базу данных слова?
Например диал. окно
Слово=
Файл Запуска=
 И как указывать путь к файлу, а то при system("D:\UnrealTournament\System\unrealtournament.exe") пишет, что файл не найден?

A. Попробуйте сделать два массива (или списка) CString - один для слов, другой для файлов. Добавляйте в массивы данные по мере ввода их пользователем. При запуске вызывайте нужный файл. Это можно более эффективно сделать с использованием ассоциативного списка (СMap), но сейчас, пожалуй, лучше не забивайте этим голову.

В С++ в строках символ "\" воспринимается как управляющий, чтобы представлять такие вещи, как "\n" - Enter, "\b"- звонок, "\0" - "косой ноль" и др. В частности, управляющая последовательность "\\" сама представляет символ "\", поэтому  в строке его надо удвоить,  а т.к. используются длинные имена, то лучше еще заключить строку в дополнительные кавычки с помощью управляющей посл-ти  \"  (хотя у меня работало и без них):

system("\"D:\\UnrealTournament\\System\\unrealtournament.exe\"");

Да, надо сказать, уважаемый Ivan, что мне дико нравится файл, который Вы запускаете ;)

 

/ / / / В ПОИСКАХ ИСТИНЫ  / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / 

Ну вот, как я и обещал - новая рубрика. Ваши вопросы, на которые я надеюсь от вас же получить ответы. Ну, поехали: 

Q. При вылизывании проекта под MS Visual C++ 5.0 была произведена замена операции заполнения служебных переменных данными из файла на диске на операцию заполнения из строковых ресурсов проекта (файл .rc). Используется ф-ция LoadString() MFC класса CString с использованием в качестве аргумента ф-ции числового идентификатора ресурса (передается не IDS_XXXX, а его числовое значение). Файл "resource.h" в необходимые файлы включен. Под VC++ 6.0 - картина аналогичная :(. Компиляция проекта при этом происходит без ошибок и предупреждений. При выполнении проекта в Debug-версии на этапе выполнения указанной выше ф-ции возникает "Debug Assertion Failed" в файле afxwin1.inl на строке 22. Этот блок из себя представляет следующее:

_AFXWIN_INLINE HINSTANCE AFXAPI AfxGetResourceHandle()
 { 
   ASSERT(afxCurrentResourceHandle != NULL);//строка 22
   return afxCurrentResourceHandle; 
 }

При нажатии на клавишу "Пропустить" программа идет дальше и вываливается на следующей операции загрузки строки с теми же симптомами и так до тех пор, пока не будут загружены все строки. После этого выполнение программы продолжается в нормальном режиме и все остальное работает как надо (строки все-таки загружаются!). В Release-версии программа пролетает это место без спотыканий.
Ясно, что можно не обращать на это внимание и сделать условную компиляцию, но дело в принципе! Не могу разобраться в чем тут закавыка.

- Евгений

По-моему, вопрос несложный, и имей я лишнее время - разобрался бы сам. Что-то тут с инициализацией, попытка использования раньше времени (как мне кажется)... Но я рассчитываю на тех, кто, прочитав это, воскликнет "ну это ж элементарно!" и сразу начнет писать ответ;) 

Те, кто задал вопрос, но пока его не увидел в рассылке и не получил личного ответа - не отчаивайтесь, ждите новых выпусков.

 

/ / / / АНОНС  / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / 

Читайте в следующих выпусках рассылки:

  •  Что дядя Билли нам готовит, или Visual Studio Next Generation

  •  WinAPI: не запутайтесь в типах

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

Ну вот, видите какой большой получился выпуск, хотя в нем, фактически, не было ничего кроме ваших писем. Думаю, это служит доказательством целесообразности нового режима выхода рассылки. Не бойтесь, что рассылка станет просто большой конференцией - я постараюсь этого не допустить! ;) Все хорошо в меру. Все ваши замечания и предложения с благодарностью принимаются.


До новых встреч. Всего хорошего!

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