Главная arrow Программирование arrow PC arrow Visual Studio C++ 2003 (v. 7.1.3088) Friday, March 24 2017  
ГлавнаяКонтактыАдминистрированиеПрограммированиеСсылки
UK-flag-ico.png English Version
GERMAN-flag-ico.png Die deutsche Version
map.gif карта сайта
нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Поделиться:

Visual Studio C++ 2003 (v. 7.1.3088) Версия для печати
Написал microsin   
18.09.2005

1. Столкнулся с ошибкой линкера LNK2019:
  ИмяПроекта.obj : error LNK2019: unresolved external symbol __imp__mciSendCommandA@16 referenced in function "long __stdcall WndProc(struct HWND__ *, unsigned int, unsigned int, long)" (?WndProc@@YGJPAUHWND__@@IIJ@Z)
 Устранил ошибку следующим образом:
  - Нашёл (через Help\Index...) подпрограмму mciSendCommand. В конце справки там была указана используемая библиотека с этой функцией - Winmm.lib.
  - Через Project\ИмяПоекта Properties...\Configuration Properties\Linker\Input\Additional Dependencies добавил в список библиотек Winmm.lib.

2. Полную информацию по процессу линковки даёт опция линкера /VERBOSE, устанавливается через Project\ИмяПоекта Properties...\Configuration Properties\Linker\General\ShowProgress\Display All Progress Messages (/VERBOSE). Иногда Help\Index... даёт неверное имя библиотеки. Тогда может помочь поиск по содержимому всех файлов c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Lib\*.lib, на появление строки с искомой функцией (для нашего примера mciSendCommand). Один из найденных файлов надо добавить в список библиотек (через Project\ИмяПроекта Properties...\Configuration Properties\Linker\Input\Additional Dependencies).

3. Можно использовать целые типы INT и UINT, которые являются производными от int и unsigned int (задаются в файле windef.h).

4. Для получения помощи по функции в коде иногда лучше не просто нажать F1, когда курсор стоит на имени функции, а скопировать имя функции в поле LookFor окна поиска Help\Index... - тогда будут видны все варианты описаний прототипов функции.

5. #include <windows.h>     // - заголовочный файл, предоставленный IDE
   #include "stdafx.h"         // - заголовочный файл, написанный пользователем (лежит в корне проекта)

6. До начала цикла обработки событий в _tWinMain помещаем код, который должен выполняться при старте приложения (выделение памяти, установка ловушек и т. д.), в теле цикла обработки событий - всё то, что работает во время жизни программы, а сразу после цикла обработки событий - код, выполняющийся при завершении программы (освобождение памяти, снятие ловушек и проч.).

7. От того, какой в каком порядке будут в имя_файла.cpp упоминаться заголовочные файлы, зависит видимость идентификаторов. Например, при таком порядке:
#include <commctrl.h>
#include "stdafx.h"
#include "ArrangeIcons.h"
#define MAX_LOADSTRING 100
   выскакивает ошибка компиляции
диск:\путь\имя_файла.cpp(55) : error C2065: 'LVM_SETITEMPOSITION' : undeclared identifier

   а при таком порядке
#include "stdafx.h"
#include <commctrl.h>
#include "ArrangeIcons.h"
#define MAX_LOADSTRING 100
   всё компилится без ошибок.

8. Генерация случайного числа от 0 до n-1:
rand()%n;

9. Создание нового класса - меню Project\Add Class... далее выбираем тип класса, далее появляется мастер для создания класса. Там указываем имя класса, базовый класс, от которого будет наследоваться новый. Нажимаем кнопку Finish.

10. Изменение поведения (кода) методов класса - открыть исходный файл *.cpp нашего нового класса, и выбрать в меню View\Properties Window (или нажать Alt+Enter). В появившемся окне нажать кнопку Overrides (на ней значок в виде зелёного кубика). Появится список методов класса. Выбираем нужный метод, выбираем из выпадающего списка <Add> имя_метода. В модуль класса вставляется пустышка, которую предстоит изменить для доработки поведения метода класса.

11. Как добавить/изменить иконку в приложении.
В конструкторе класса окна m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON1);

12. Что происходит, когда мы добавляем (привязываем) переменную к ресурсу (например, полю ввода).
Когда мы добавляем переменную, связанную с ресурсом окна ввода, то на самом деле в *.h-файл, описывающий класс главного окна (например, диалога, в котором есть окно ввода), добавляется в раздел public класса главного окна добавляется переменная. В моём примере *.h-файл ciconfigDlg.h описывал класс главного диалогового окна CciconfigDlg (тип CDialog), и в этом файле в раздел public описания класса CciconfigDlg добавлялась переменная
CEdit IPaddress.

13. Два метода получить текст из переменной типа CEdit (переменной, связанной с окном ввода).
   В нашем случае переменная, куда считываем текст, это CString ip, а переменная CEdit IPaddress связана с ресурсом окна ввода IDC_EDIT1.
CString ip;
GetDlgItemText (IDC_EDIT1, ip);
IPaddress.GetWindowText(ip);

14. Когда меняем свойства ресурса, то меняется его текстовое описание в файле *.rc. Например, имеем IDC_LIST1 (окно - список вывода) и связанную с ним переменную CListBox LogList. Если у IDC_LIST1 свойство Sort == False, то описание у него в *.rc-файле будет таким:
LISTBOX  IDC_LIST1,7,36,306,157,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
   Если Sort == True, то таким:
LISTBOX IDC_LIST1,7,36,306,157,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP

15. Указатель на экземпляр класса диалогового окна можно получить с помощью ключевого слова this:
void CciconfigDlg::OnBnClickedButton1()
{
// TODO: Add your control notification handler code here
...
pSocket = new CClientSocket(this);
...
}
   В этом примере вызов конструктора CClientSocket требует параметра, который содержит указатель на главное окно диалога (им инициализируется внутренняя переменная).

16. Ошибка Compiler Error C2582 'operator =' function is unavailable in 'class' возникает, когда в правой части стоит указатель на экземпляр класса, в котором нет определения оператора "=". Например, наш класс, не имеющий этого оператора CciconfigDlg. На выражение присваивания
CClientSocket::CClientSocket(CciconfigDlg* Dlg)
{
m_Dlg = Dlg;
}
   генерится ошибка C2582. Тогда лечится проблема добавлением определения оператора "=" в заголовочный файл класса CciconfigDlg.h:
public:
...
void operator = (const CciconfigDlg& bbb){};
   Здесь bbb произвольное имя. Кроме того, эта ошибка может возникнуть тогда, когда в левой части выражения с "=" стоит не указатель на экземпляр класса CciconfigDlg, а сам экземпляр класса, например, переменная m_Dlg определена ошибочно вот так:
class CClientSocket : public CSocket
{
public:
..
protected:
CciconfigDlg m_Dlg;
};
   а нужно вот так, тогда ошибки C2582 не будет даже без определения оператора "=" в классе CciconfigDlg:
class CClientSocket : public CSocket
{
public:
..
protected:
CciconfigDlg* m_Dlg;
};

17. Текстовую метку можно поменять командой:
m_Dlg->SetDlgItemText(IDC_STATIC, "new label text");
   где m_Dlg - указатель на экземпляр класса окна диалога, в котором размещена данная метка.

18. При работе с CSocket нужно быть осторожным с ->Close() и delete - например, эти функции вызывать в обработчике кнопки, в котором создался экземпляр CSocket, иначе не получает управления обработчик приёма ::OnReceive.

19. Нельзя никакие операции, вызывающие блокировку, выполнять в обработчиках событий кнопок, иначе приложение "виснет", пока не закончится операция. Такие операции нужно запускать в отдельном потоке.

20. Использование WinAPI без отключения поддержки MFC 
   Частенько бывает что нужно воспользоваться некоторыми функциями WinAPI, но если проект создается с поддержкой MFC, то ничего не выходит. Для вызова функций WinApi из MFC используй оператор :: перед именем функции, например ::GetWindowText(hWnd, ....

21. Использование опции вывода "wt" подпрограммы fopen выводит в файл дополнительные символы \r в конце каждой строки.

22. Как запустить упаковку *.exe-файла после компиляции релиза. В Visual Studio есть очень удобный менеджер конфигураций проекта. Конфигурация - это набор установок для компилируемой и линкуемой программы. По умолчанию существует 2 конфигурации - Debug и Release, назначение которых очевидно из названия. Когда я научился их применять (достаточно в тулбаре выбрать одну из них в выпадающем списке) - одну для отладки, другую для получения готового приложения, то мне захотелось автоматически упаковывать exe-шник релиза упаковщиком AsPack. Оказалось, для этого можно воспользоваться Build Evens:
- Project\имя_приложения Properties..., выбираем тип конфигурации Release
- Build Events\Post Build Event\Command Line, вставляем туда "c:\Program Files\AsPack\ASPack.exe" "$(ProjectDir)$(OutDir)\$(TargetFileName)" /Q /B-
  Здесь "$(ProjectDir)$(OutDir)\$(TargetFileName)" означает полный путь к упаковываемому экзешнику (путь этот составлен из специальных макроопределений Visual Studio), а /Q /B- просто опции для AsPack, которые указывают без вопросов всё сделать и не делать бэкапа.
- для отладки макроопределений удобно в командную строку Build Event подставить команду echo макроопределение(я) >testfile.txt. В результате в файле testfile.txt можно прочитать, что подставляется вместо макроопределения.

23. Глобальные переменные в C++ можно определять как внутри основного тела программы _tmain, так и до нее - сразу после одной или нескольких (если есть) строк include. Инициализировать переменные, объявленные до _tmain, можно только вместе с объявлением.

24. Уже существующие (написанные ранее) процедуры из файлов *.cpp удобно использовать следующим образом:
- наш *.cpp файлик с нужными функциями переписываем в корневую папку проекта;
- добавляем *.cpp файл в проект - в Solution Explorer щёлкаем правой кнопкой на папке Source Files, выбираем
  Add\Add Existing Item..., выбираем наш файл *.cpp;
- в тело модуля, где вызывается процедура любая процедура из *.cpp (в этом примере процедура WLog), вставляем в глобальный блок объявления extern:
extern void WLog (CString S);

25. В процессе написания программы целесообразно изредка переключаться в режим компилирования Release, потому что при этом могут вылезать неожиданные ошибки (не проявляющие себя в режиме компилирования Debug).

26. compound-statement означает простой набор операторов за фигурными скобками (или один оператор).

27. Заморочки с константами типа char.
Оказывается, любые константы, у которых не указана разрядность (или тип), по умолчанию считаются компилятором за 32-х разрядные, то есть, например, если мы укажем 0xE0, то это на самом деле будет означать 0x000000E0. На первый взгляд, ну и что тут такого? Однако из-за приведения типов это может порождать непредсказуемый результат. Например, вариант 1 нерабочий, а вариант 2 работает нормально.
//Вариант 1:
...
char ch0;
...

switch (ch0)
{
case 0x00:
...
break;
case 0xE0:
...
break;
default:
  ...
}
//Вариант 2:
...
char ch0;
...

switch (ch0)
{
case (char)0x00:
...
break;
case (char)0xE0:
...
break;
default:
  ...
}
В варианте 1 по метке case 0xE0 управление НИКОГДА не передаётся, независимо от значения переменно1 ch0. Дизассемблирование показывает, что в начале оператора switch происходит приведение переменной к типу int с помощью инструкции movsx, которая заполняет все старшие биты слова битом 7 значения 0xE0, то есть получается при этом 0xFFFFFFE0. Далее это значение по метке case 0xE0 уже сравнивается с 0x0000000E0. Пример дизассемблированного кода варианта 1:
switch (ch0)
0049436A movsx      eax,byte ptr [ch0] ;тут 0xE0 превращается в 0xFFFFFFE0
0049436E mov        dword ptr [ebp-190h],eax
00494374 cmp        dword ptr [ebp-190h],0
0049437B je         0049438B
0049437D cmp        dword ptr [ebp-190h],0E0h; а тут сравн. 0xFFFFFFE0 с 0x000000E0
00494387 je         00494391 ; по метке 00494391 управление НИКОГДА не передается
00494389 jmp        00494397
{
case 0x00:
...
0049438B ...
break;
0049438F jmp        004943A3
case 0xE0:
...
00494391 ...
break;
00494395 jmp        004943A3
default:
  ...
00494397 ...
0049439A ...
0049439B ...
004943A0 ...
}
004943A3 jmp        ...

Само собой, 0xFFFFFFE0 никогда не равняется 0x0000000E0, и код не работает.
В заключение привожу правильный код варианта 2:

switch (ch0)
0049436A mov        al,byte ptr [ch0]
0049436D mov        byte ptr [ebp-190h],al
00494373 cmp        byte ptr [ebp-190h],0E0h
0049437A je         0049438D
0049437C cmp        byte ptr [ebp-190h],0
00494383 je         00494387
00494385 jmp        00494393
{
case (char)0x00:
...
00494387 ...
break;
0049438B jmp        0049439F
case (char)0xE0:
...
0049438D ...
break;
00494391 jmp        0049439F
default:
  ...
00494393 ...
00494396 ...
00494397 ...
0049439C ...
}

0049439F jmp        ...

Другой метод побороть проблему - применить тип переменной unsigned.

28. Перехват Ctrl-Break в консольном приложении можно установить с помощью функции SetConsoleCtrlHandler - позволяет, кроме того, реагировать на Ctrl-C и события закрытия консоли. Пример:
1. Описываем обработчик консоли:
BOOL CtrlHandler( DWORD fdwCtrlType )
/* Обработчик исключительных ситуаций консоли,
   типа нажатия на Ctrl-Break или щелчка
   на кнопке с крестиком */
{
  CString ctrl_message_descr;
  switch( fdwCtrlType )
  {
   // Handle the CTRL-C signal.
   case CTRL_C_EVENT:
     //поскольку мы запустили поток, обрабатывающий клавиатурные
  // нажатия, сюда управление никогда не попадает
     ctrl_message_descr = "Ctrl-C event";
  WLog   (ctrl_message_descr);
     return( TRUE );
 
   // CTRL-CLOSE: confirm that the user wants to exit.
   case CTRL_CLOSE_EVENT:
     ctrl_message_descr = "Ctrl-Close event";
  WLog   (ctrl_message_descr);
  CleanUp();
  bStop = true;
     Beep( 600, 200 );
     return ( FALSE );
 
   // Pass other signals to the next handler.
   case CTRL_BREAK_EVENT:
     ctrl_message_descr = "Ctrl-Break event";
  WLog   (ctrl_message_descr);
     printf (ctrl_message_descr);
  CleanUp();
  bStop = true;
     Beep( 900, 200 );
     return FALSE;
 
   case CTRL_LOGOFF_EVENT:
     ctrl_message_descr = "Ctrl-Logoff event";
  WLog   (ctrl_message_descr);
     printf (ctrl_message_descr);
  CleanUp();
  bStop = true;
     Beep( 1000, 200 );
     return FALSE;
 
   case CTRL_SHUTDOWN_EVENT:
     ctrl_message_descr = "Ctrl-Shutdown event";
  WLog   (ctrl_message_descr);
     printf (ctrl_message_descr);
  CleanUp();
  bStop = true;
     Beep( 750, 500 );
     return FALSE;
 
   default:
     ctrl_message_descr = "Uncnown event!!!";
  WLog   (ctrl_message_descr);
     printf (ctrl_message_descr);
  CleanUp();
  bStop = true;
     return FALSE;
  }
}

2. Инициализация в начале main:
...
if( SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE ) )
{
   if (iDebugLevel == 1)
       WLog("Control Handler (Ctrl-Break interception) is installed.");
}
else
{
   messtr = "ERROR: Could not set control handler";
   WLog(messtr);
   printf(messtr);
   nRetCode = 2;
   Error = true;
}
...

См. также, как выполнять
действия при выходе из программы Win32 (функция atexit()).

29. В dialog-based MFC приложении размер диалоговой формы задается в rc-файле, в строке с ключевым словом DIALOGEX, например:
IDD_QOSTEST_DIALOG DIALOGEX 0, 0, 402, 236
   где QOSTEST - имя класса окна, 402 - размер по горизонтали, 236 - по вертикали.
Окно About получает свои размеры аналогично:
IDD_ABOUTBOX DIALOGEX 0, 0, 235, 55
Чтобы точно изменить размеры окон, придется вручную подредактировать числа, в самом Visual Studio можно менять размеры только перетаскиванием мышью.

30. Многострочное окно текста можно выводить, воспользовавшись простым Edit Control (обычно он используется для редактирования однострочного текста). По шагам:
- перетаскиваем его на конструируемое окно.
- меняем размеры перетаскиванием границ. Для того, чтобы выбрать наш Edit Control, можно пользоваться выпадающим списком панели Properties (иногда иначе не получается, например, если Edit Control лежит поверх какого-нибудь Tab Control).
- устанавливаем у Edit Control свойство Multiline = True
- при необходимости включаем полосы прокрутки (Horizontal Scroll и/или Vertical Scroll присваиваем True)
- привязываем к нашему Edit Control переменную - правой кнопкой Add Variable..., из выпадающего списка Control ID выбираем наш Edit Control (например IDC_EDIT1), указываем Variable name (например, edVar).
- для вывода текста используем метод SetWindowText (смотри также другие методы класса CEdit в справке Visual Studio), например:
edVar.SetWindowText("Hello World!");
Чтобы вывести несколько строк, используйте разделитель \r\n, например
edVar.SetWindowText("Line 1\r\nLine 2");
Чтобы очистить окно текста, используем
edVar.SetWindowText("");
Чтобы добавить текст в конец, используем
CString csTempText;
edVar.GetWindowText(csTempText);
csTempText += "этот текст добавим ";
edVar.SetWindowText(csTempText);

Чтобы прокрутить текст, например, до конца:
::SendMessage(edVar.m_hWnd, WM_VSCROLL, SB_BOTTOM, 0);
Чтобы прокрутить текст на одну строку:
::SendMessage(edVar.m_hWnd, WM_VSCROLL, SB_LINEDOWN, 0);
 Это Вам не Delfi и не CBuilder!

31. Как использовать закладки (CTabCtrl).
- создаем новый проект, тип проекта Visual C++ Projects\MFC\MFC Application, даем название проекту (пусть это будет test003).
- открывается MFC Application Wizard. Выбираем Application Type\Dialog based, Use MFC in a static library, жмем Finish.
- открываем Resource View, выбираем test003\test003.rc\Dialog\IDD_TEST003_DIALOG, двойным щелчком открываем редактор диалога.
- удаляем с формы диалога "TODO: Place dialog controls here.". Находим на Toolbox элемент Tab Control, перетаскиваем его на форму диалога, меняем размеры по вкусу.
- привязываем к нашему Tab Control переменную - правая кнопка на нём, выбираем Add Variable..., Variable name указываем m_Tabs, жмем Finish.
- Теперь если запустить программу, то у Tab Control закладок не будет. Нужен код, который изначально создает 2 закладки. Этот код добавляем в место, где инициализируется наш диалог - в метод Ctest003Dlg::OnInitDialog(),
после подсказки "TODO: Add extra initialization here":
   TC_ITEM tci;   // эта структура нужна для вставки закладки
   memset(&tci,0,sizeof(tci));// очистка структуры
   tci.mask = TCIF_TEXT;    // у закладки будет только текст
   tci.pszText = "Закладка 1";
   m_Tabs.InsertItem(0, &tci); // первая закладка имеет индекс 0
   tci.pszText = "Закладка 2";
   m_Tabs.InsertItem(1, &tci);  // вставляем вторую закладку
- если запустить программу, то увидим закладки. Для того, чтобы поместить на закладки содержимое, нужно предварительно для каждой закладки подготовить форму диалога и нарисовать на каждой форме свои элементы управления. Для этого в Resource View на папке Dialog щелкаем правую кнопку, выбираем Add Resource..., в появившемся окошке выбираем Dialog и нажимаем кнопку New. Переименуем наш ресурс - Resource View, выбираем test003\test003.rc\Dialog\, правый щелчок на IDD_DIALOG1, выбираем Properties. Свойство ID содержит имя IDD_DIALOG1, меняем его на IDD_TABPAGE1.
- меняем свойства и содержимое нашей закладки - двойной щелчок на Resource View\test003\test003.rc\Dialog\IDD_TABPAGE1, в панели Properties откроются свойства нашей закладки. Меняем свойство Style на Child, свойство Border на None, удаляем кнопки Ok и Cancel, чтобы осталась чистая форма.
- делаем копию созданной закладки - правая кнопка на Resource View\test003\test003.rc\Dialog\IDD_TABPAGE1, выбираем Copy, правая кнопка на Resource View\test003\test003.rc\Dialog, выбираем Paste. Появляется IDD_TABPAGE2.
- добавляем элементы управления, которые нам нужны - предположим, на первой страничке будет строка редактирования Edit Control, а на второй страничке будет IP Address Control. Перетаскиваем с Toolbox соответствующие элементы на каждую страничку IDD_TABPAGE1 и IDD_TABPAGE2.
- добавляем к каждой страничке свой класс - правый щелчок на страничке в визуальном редакторе, выбираем Add Class..., указываем Class name CTabPage1, Base Class выбираем CDialog, жмем Finish. Для второй странички делаем тоже самое, имя класса указываем CTabPage2. Эти классы нужны, чтобы в обработчике смены закладок запускать создание соответствующей закладки вызовом конструктора класса (например, new CTabPage1).
- чтобы классы были CTabPage1 и CTabPage2 были видны в реализации основного класса диалога программы, в начало файла test003Dlg.cpp добавим строчки
#include "TabPage1.h"
#include "TabPage2.h"
- добавляем в описание класса Ctest003Dlg (файл test003Dlg.h) переменную m_pTabDialog - указатель на память, в которой будет храниться отображаемая страничка Tab Control (этот указатель присваивается вызовом конструктора классов CTabPage1 и CTabPage2):
class Ctest003Dlg : public CDialog
{
  ...
public:
CTabCtrl m_Tabs;
CDialog* m_pTabDialog; // <--- добавить
  ...
};
В конструктор класса Ctest003Dlg (файл test003Dlg.cpp) Добавим код, обнуляющий эту переменную:
Ctest003Dlg::Ctest003Dlg(CWnd* pParent /*=NULL*/)
       :CDialog(Ctest003Dlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_pTabDialog = 0; // <--- добавить
}
- Нужно добавить обработчик события смены закладки. Двойным щелчком открываем Resource View\test003\test003.rc\Dialog\IDD_TEST003_DIALOG (нашу главную форму диалога), правая кнопка на Tab Control, выбираем Add Event Handler..., проследим, чтобы в окне Message type было выбрано TCN_SELCHANGE, жмем кнопку Add and Edit.
- Теперь добавим код в обработчик события смены закладки:
void Ctest003Dlg::OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESULT *pResult)
{
  // TODO: Add your control notification handler code here
  int id; // ID диалога

  // надо сначала удалить предыдущий диалог в Tab Control'е:
  if (m_pTabDialog)
  {
   m_pTabDialog->DestroyWindow();
   delete m_pTabDialog;
  }

  // теперь в зависимости от того, какая закладка выбрана,
  // выбираем соотв. диалог
  switch( m_Tab.GetCurSel()+1 ) // +1 для того, чтобы порядковые номера закладок
                               // и диалогов совпадали с номерами в case
  {
   // первая закладка
   case 1 :
         id = IDD_TABPAGE1;
         m_pTabDialog = new CTabPage1;//вызываем конструктор класса
           // тип указателя соответствует нужному диалогу,
           // иначе добавленный в диалог код не будет функционировать
   break;

   // вторая закладка
   case 2 :
         id = IDD_TABPAGE2;
         m_pTabDialog = new CTabPage2;//вызываем конструктор класса
   break;

   // все остальные закладки, если они есть,
   // будут здесь тоже представлены, каждая - отдельным case

   // а если обработка такого номера не предусмотрена
   default:
       m_pTabDialog = new CDialog; // новый пустой диалог
       return;

   } // end switch

  // создаем диалог
  m_pTabDialog->Create (id, (CWnd*)&m_Tabs); //параметры: ресурс диалога и родитель

  CRect rc;

  m_Tab.GetWindowRect (&rc); // получаем "рабочую область"
  m_Tab.ScreenToClient (&rc); // преобразуем в относительные координаты

  // исключаем область, где отображаются названия закладок:
  m_Tab.AdjustRect (FALSE, &rc);

  // помещаем диалог на место..
  m_pTabDialog->MoveWindow (&rc);

  // и показываем:
  m_pTabDialog->ShowWindow ( SW_SHOWNORMAL );
  m_pTabDialog->UpdateWindow();

  *pResult = 0;
}
- все, программа работоспособна, за исключением того, что при запуске не отображается содержимое первой закладки. Для этого в BOOL Ctest003Dlg::OnInitDialog() нужно добавить код:
BOOL Ctest003Dlg::OnInitDialog()
{
   ...
   m_Tabs.InsertItem(1, &tci); // вставляем вторую закладку
 //-----------------
 // добавить:
 NMHDR hdr;

 hdr.code = TCN_SELCHANGE;
 hdr.hwndFrom = m_Tabs.m_hWnd;
 SendMessage ( WM_NOTIFY, m_Tabs.GetDlgCtrlID(), (LPARAM)&hdr );
 //-----------------
   ...
}
  Можно еще проще:
BOOL Ctest003Dlg::OnInitDialog()
{
   ...
   m_Tabs.InsertItem(1, &tci); // вставляем вторую закладку
 //-----------------
 // добавить:
 LRESULT dummy_var;
 OnTcnSelchangeTab1(NULL, &dummy_var);
 //-----------------
   ...
}
  Ну что, в тоске вспоминаете Delphi и CBuilder =)? Это Вам не хухры-мухры, это Visual Studio, почувствуйте разницу!

 
Использовалась статья http://gzip.rsdn.ru/archive/vc/issues/pvc012.htm.

32. Как удалить ненужный класс из списка классов проекта (Class View)?
  Это можно сделать, удалив из дерева Solution Explorer\имя_проекта\Source Files\имя_класа.cpp и Solution Explorer\имя_проекта\Header Files\имя_класа.h. Соответствующий класс автоматически пропадет из списка Class View. При этом файлы имя_класа.cpp и имя_класа.h с диска не удаляются, это нужно сделать вручную. В противном случае при попытке создать класс с тем же именем появится предупреждающее сообщение.

33. Использование CComboBox
- чтобы поменять высоту выпадающего списка, нужно щелкнуть в визуальном редакторе ресурсов на стрелочку вниз в CComboBox. Появится возможность поменять высоту выпадающего списка перетаскиванием нижнего квадратика. При этом в файле *.rc, в разделе "Dialog" меняется значение высоты выпадающего списка (здесь число 134):
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
...
BEGIN
   ...
   COMBOBOX IDC_COMBO2,33,22,280,134,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP
   ...
END
...
- свойство Type меняет поведение CComboBox. Если выбран тип Dropdown, то в окне редактирования можно вводить текст, если выбрано Drop List, то вводить ничего нельзя, можно только выбирать из списка.
- в свойство Data можно добавлять свои строки в режиме редактирования ресурса, каждая строка отделяется от другой точкой с запятой.
- в режиме выполнения очищается список функцией ResetContent(), добавляются строки в конец списка AddString(), вставляются InsertString(). В InsertString() указывается индекс строки, перед которой будет вставка, если указать -1, то вставка будет в конец, по аналогии с AddString().
- SetCurSel() выбирает строку, параметром является индекс выбираемой строки. 0 означает 1-ю строку, 1 - вторую и т. д. (если указать -1, то не будет выбрана никакая строка).

34. Как выполнить действия по завершении программы.
   Для этого в момент инициализации (для приложения - диалогового окна это будет, например, тело BOOL имя_класса_диалога::OnInitDialog()) запускается подпрограмма atexit(). Пример:
...
void OnExit (void)
{
Beep(4000, 200);
}
...
BOOL Cping5bDlg::OnInitDialog()
{
       ...
// TODO: Add extra initialization here
       ...
atexit(OnExit);

return TRUE; // return TRUE unless you set the focus to a control
}

Можно также использовать функцию
_onexit(), пример:
int OnExit ()
{
Beep(4000, 200);
CString param_list;
param_list = "";
   for (int i=0; i<Include.GetSize();i++)
param_list += Include.GetAt(i) + ";";
csSaveIni("common_settings", "Include", param_list);
return 0;
}
...
BOOL CsyslogvDlg::OnInitDialog()
{
       ...
// TODO: Add extra initialization here
       ...
_onexit(OnExit);

return TRUE; // return TRUE unless you set the focus to a control
} 

Ограничения - в теле OnExit нельзя использовать переменные, связанные с окнами, поскольку они уже уничтожены; нужно использовать заранее заполненные глобальные переменные (в этом примере массив Include.). Кроме того, у меня не получилось объявить метод OnExit как экземпляр класса окна CsyslogvDlg (в целях доступа к переменным класса), пришлось сделать простую глобальную процедуру.
См. также пример, как
перехватывать момент выхода из консоли (функция SetConsoleCtrlHandler()).
 

35. Ошибка компилятора C1010.
   Окно Output выглядит примерно так:

Сompiling...
имя_файла.cpp
c:\VisualStudioProjects\ping5b\имя_файла.cpp(33) : fatal error C1010: unexpected end of file while looking for precompiled header directive

Build log was saved at "file://c:\VisualStudioProjects\ping5b\Debug\BuildLog.htm"
ping5b - 1 error(s), 0 warning(s)

---------------------- Done ----------------------
   Build: 0 succeeded, 1 failed, 0 skipped


   У меня эта ошибка возникала, когда в начало файла имя_файла.cpp забыл вставить строчку #include "stdafx.h".

36. Как получить имя исполняемого файла.
   Нужно добраться до свойств объекта CWinApp, а там обилие возможностей - например, у него есть переменная m_pszExeName, которая как раз и содержит имя исполняемого файла без расширения. Если, например, у вас приложение типа MFC-диалог, то у него обязательно есть класс приложения, обычно называемый Cимя_программыApp (реализация класса в имя_программы.cpp, заголовочный файл имя_программы.h). Этот класс Cимя_программыApp является производным от класса CWinApp (задается в имя_программы.h). Экземпляр класса объявлен в имя_программы.cpp, и обычно носит имя theApp. Что нужно сделать по шагам:
- узнаем название класса приложения (виден в корне дерева Class View, название класса заканчивается на App). Например, название класса Cping5bApp.
- ищем название экземпляра класса (переменной) - либо в помощью поиска по файлам на название класса Cping5bApp, либо открыв файл определения класса, в нашем случае это будет ping5b.cpp. В этом файле будет строка, в которой и будет задаваться переменная класса:
Cping5bApp theApp;
- в файле, где нужно обратиться к theApp.m_pszExeName, нужно добавить в начало строку для нашего примера:
#include "ping5b.h"
  теперь в этом файле можно обращаться к переменным и методам объекта theApp.

37. Как запретить кнопку или элемент управления
CWnd->EnableWindow(false); //запрещает окно (кнопка, список, любой Control)
CWnd->EnableWindow(true);  //разрешает окно (кнопка, список, любой Control)
CWnd->IsWindowEnabled();   //получить состояние запрещенности окна
Поскольку все Control-ы являются наследниками класса CWnd, то в вышеуказанных выражениях нужно просто подставить указатель на переменную нужного Control.

38. Как поменять текст на кнопке
pButton->SetWindowText("Текст на кнопке");

39. Простейший способ рисовать текст на форме:
void CTestPaintDlg::OnButton1()
{
  CDC* dc;
  dc=GetDC();
  dc->TextOut(20,20,"GetDC() example"); 
}
У этого метода куча недостатков. Текст выводится некультяпистым шрифтом на белом фоне, так что на серой форме это будет смотреться некрасиво - происходит из-за того, что по умолчанию используется для вывода непрозрачный фон (OPAQUE). Тот же эффект дает вызов
dc->SetBkMode(OPAQUE);
Чтобы это поправить, можно использовать
dc->SetBkMode(TRANSPARENT);
Теперь фон текста будет прозрачный.

Текст рисуется поверх всех элементов управления на окне, как будто их и не было. Если текст попадает при выводе за пределы окна, он обрезается или не прорисовывается.
При передвижении окна или перерисовке нарисованный текст будет пропадать. Это легко исправить, если код поместить в OnPaint.
void CTestPaintDlg::OnPaint()
{
   ...........
   }
   else
   {
       CDialog::OnPaint();
   }
   CDC* dc;
   dc=GetDC();
   dc->TextOut(20,20,"GetDC() example");
}

Кроме GetDC, существует еще GetWindowDC:
      
CDC* dc;
   dc=GetWindowDC();
   dc->TextOut(20,20,"dc=GetWindowDC() example");

Разница в работе GetWindowDC() и GetDC() в том, что GetDC() выводит тест относительно верхнего левого угла
серой формы, сразу под голубой плашкой окна, а GetWindowDC() выводит тест относительно верхнего левого угла всего окна целиком, вместе с плашкой.

40. Вывод окна запроса с помощью MessageBox
Есть как минимум две разновидности этой Функции
int CWnd::MessageBox (
   LPCTSTR lpszText,
   LPCTSTR lpszCaption = NULL,
   UINT nType = MB_OK
);
и
int MessageBox(
   HWND hWnd,
   LPCTSTR lpText,
   LPCTSTR lpCaption,
   UINT uType
);

Вторую разновидность нужно вызывать как ::MessageBox, иначе она в зависимости от контекста окажется принадлежащей какому-нибудь оконному классу.
Если вместо hWnd указать NULL, то отображаемое окно не будет иметь родительского окна. Это означает, что он становится "независимым" от основной программы - и к окну программы, и к окошку MessageBox можно одновременно получить доступ через интерфейс GUI. Если же в качестве параметра hWnd указать m_hWnd, то появляющееся окно MessageBox станет модальным - не закрыв его, нельзя получить доступ в главное окно. Остальные параметры у обоих функций MessageBox эквивалентны, как и возвращаемые значения, и подробно описаны в MSDN.

41. ExitProcess() полностью, немедленно завершает программу, даже если ExitProcess() вызвать из потока, запущенного в программе. Причем функция, настроенная на обработку выхода с помощью atexit(), не срабатывает.

42. Класс CListCtrl предоставляет возможность создать список а-ля списка окна Explorer - объектами списка могут быть маленькие иконки, большие иконки (туда можно загрузить картинку), список может быть простой и расширенный, в виде таблицы. Отображение зависит от стиля, который можно назначить при разработке программы (в окне свойств Properties меняем значение свойства View, выбрав из выпадающего списка один из вариантов Icon, Small Icon, List или Report).
Манипулируем экземпляром CListCtrl, привязав, как обычно, к нему переменную, и потом в коде обращаемся к методам этой переменной. Вот примеры действий над экземпляром CListCtrl:
1. Получить размеры
CRect rect;
m_cListCtrl.GetClientRect(&rect);
2. Добавить столбцы (только если стиль выбран в Report)
int nColInterval = rect.Width()/10;
m_cListCtrl.InsertColumn(0, _T("Time"), LVCFMT_LEFT, nColInterval);
m_cListCtrl.InsertColumn(1, _T("IP"), LVCFMT_LEFT, nColInterval*2);
m_cListCtrl.InsertColumn(2, _T("Event"), LVCFMT_LEFT, nColInterval*2);
m_cListCtrl.InsertColumn(3, _T("Description"), LVCFMT_LEFT, rect.Width() - 5*nColInterval);
3. Вставляем экземпляры объектов в CListCtrl
LVITEM lvi;
CString strItem;
// Insert the first item
lvi.mask = LVIF_IMAGE | LVIF_TEXT;
strItem.Format("%s",evnt.time);
lvi.iItem = idx;
lvi.iSubItem = 0;
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_cListCtrl.InsertItem(&lvi);
// Set subitem 1
strItem.Format("%s", evnt.IP);
lvi.iSubItem =1;
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_cListCtrl.SetItem(&lvi);
// Set subitem 2
strItem.Format("%s", evnt.evID);
lvi.iSubItem =2;
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_cListCtrl.SetItem(&lvi);
// Set subitem 3
strItem.Format("%s", evnt.evDescr);
lvi.iSubItem =3;
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_cListCtrl.SetItem(&lvi);
4. Очистка
m_cListCtrl.DeleteAllItems()

43. Массив строк CString.
Класс CStringArray позволяет работать с динамическим массивом строк. Вот примеры действий с таким массивом:
1. Очистка
CStringArray csArray;
csArray.RemoveAll();
2. Добавление строки в массив
csArray.Add( "Эту строку добавляем в массив" );
3. Получение количества строк в массиве, получение строк из массива (индексация элементов, начиная с 0):
for (int i=0; i<csArray.GetSize();i++)
   MessageBox (NULL, csArray.GetAt(i), "", MB_OK);

44. Как определить размер объекта (массива, переменной, класса) в байтах.
CSliderCtrl sl;
...
int size_in_bytes = sizeof(sl);

45. Как определить размер массива (количество элементов в массиве).
CSliderCtrl sl[9];
...
int elements_in_array = sizeof(sl) / sizeof(sl[0]);  // ==9
46. Как получить имя файла приложения
CString LogFileName;
LogFileName = theApp.m_pszExeName + (CString)".log";

47. Миграция консольных программ с CBuilder на MFC
[Строки]
AnsiString             -> CString
byte                   -> char
ParamStr(param_num)    -> argv[param_num]
AnsiString.Pos(строка) -> CString.Find(строка)
AnsiString.SubString() -> CString.Left() или CString.Mid()
AnsiString.Length()    -> CString.GetLength()
AnsiString.UpperCase() -> CString.MakeUpper()

При работе со строками важно учитывать следующее отличие AnsiString и CString - нумерация символов в AnsiString начинается с 1, а у CString с 0. Например, так нужно получить последний символ и удалить его в AnsiString:
 char sym = asPattern[asPattern.Length()];
 asPattern.Delete(asPattern.Length(), 1);

А так делаем все то же самое в CString:
 char sym = asPattern[asPattern.Length() - 1];
 asPattern.Delete(asPattern.Length() - 1, 1);

[Дата и время]
DecodeDate(), Date() -> CTime, CTime::GetCurrentTime(), CTime.GetYear(), CTime.GetMonth(), CTime.GetDay(), CTime.Format()
Пример на CBuilder:
 WORD Year, Month, Day;
 AnsiString asYear, asMonth, asDay;
 DecodeDate(Date(), Year, Month, Day);
 asYear  = IntToStr(Year);
 asMonth = IntToStr(Month);
 asDay   = IntToStr(Day);

То же самое на Visual C:

 WORD Year, Month, Day;
 CString asYear, asMonth, asDay;
 CTime ctTime = CTime::GetCurrentTime();
 Year  = ctTime.GetYear();
 Month = ctTime.GetMonth();
 Day   = ctTime.GetDay();
 asYear  = ctTime.Format( "%Y" );
 asMonth = ctTime.Format( "%m" );
 asDay   = ctTime.Format( "%d" );

[Файлы]
1. ExtractFileName() -> _splitpath()
   ExtractFileDir () -> _splitpath()
Пример на CBuilder:
 AnsiString asDir, asName;
 asName = ExtractFileName(asFullPath);
 asDir = ExtractFileDir (asFullPath);
 AnsiString asOldName, asNewName;
 asOldName = asFullPath;
 asNewName = asDatePart + asName;
 if (!asDir.IsEmpty())
   asNewName = asDir + "\\" + asNewName;

Пример на Visual C:
 CString csDrive, csDir, csName, csExt;
 char drive;
 char dir[256];
 char fname[256];
 char ext[256];
 _splitpath(csFullPath, &drive, dir, fname, ext);
 csDrive = (CString)drive;
 csDir   = (CString)dir;
 csName  = (CString)fname;
 csExt   = (CString)ext;
 if (csDrive != "")
   csDir  = csDrive + ":" + csDir;
 CString csOldName, csNewName;
 csOldName = csFullPath;
 csNewName = csDatePart + csName + csExt;
 if (!csDir.IsEmpty())
   csNewName = csDir + "\\" + csNewName;

2. FileExists()  -> PathFileExists() (#include "shlwapi.h")
3. FileGetAttr() -> GetFileAttributes()
4. FileSetAttr() -> SetFileAttributes()
5. DeleteFile()  -> DeleteFile()
6. FileOpen()    -> _open()
7. Получение длины файла
   FileSeek() -> _filelength()
8. FileSeek() -> _lseek()
9. FileRead() -> _read()
10. FileWrite() -> _write()
11. FileClose() -> _close()
12. RenameFile() -> rename()
13. CopyFile()   -> CopyFile()

48. Добавление в программу регулярных выражений.
1. Скачиваем RegexVc71.EXE (см. Download на
http://www.tropicsoft.com/Components/RegularExpression/index.html ).
2. Устанавливаем компонент Regular Expression Component Library Vc71 в любую папку, я поставил в
   C:\Program Files\Microsoft Visual Studio .NET 2003\Regular Expression Component Library Vc71\
3. В Visual Studio C++ 7.1 делаем File\New Project...\Project Types:Win32\Win32 Console Project, указываем имя проекта, например, regextest, Жмем OK, в Application Settings должно быть выбрано Console application, и можно поставить галочку на Add support for:MFC, жмем Finish.
4. Добавляем
#include <RegularExpressionClass.h>
   В Project\Properties\Configuration Properties\C/C++\General\Additional Include Directories
   добавляем "$(VSInstallDir)\Regular Expression Component Library Vc71\Include"
5. В Project\Properties\Configuration Properties\Linker\General\Additional Library Directories добавляем
   "$(VSInstallDir)\Regular Expression Component Library Vc71\Lib"
4. В теле main или до него определяем экземпляр класса регулярного выражения
5. В раздел // TODO: code your application's behavior here. добавляем код, использующий класс (код чисто тестовый):
RegexClass::RegularExpression re;
CString csPattern = "YYMMDD";
CString csRegExp = "(Y{2,4}|M{2}|D{2}|h{2}|m{2}|s{2}|.){1,}";
re.SetExpression(csRegExp.GetBuffer());
re.StringToMatch = csPattern.GetBuffer();
if (re.MatchAll())
     MessageBox(NULL, "Matched!", "O.K.", MB_OK);
else
     MessageBox(NULL, "No match...", "Err.", MB_ICONASTERISK);
6. Все это мы проделали в конфигурации Debug. Для конечного релиза нужно сделать то же самое, только еще целесообразно в Project\Properties\Configuration Properties\General\Use of MFC\ выбрать Use MFC in a Static Library, а также для предотвращения ошибки Linker Tools Error LNK2005 в Configuration Properties\Linker\Command Line\Additional Options:\ добавляем /FORCE:MULTIPLE (см. совет
Ошибка линкера LNK2005).
7. Преобразование текста в IP.
void TEXTtoIP (CString csIP, byte* addr)
{
//проверка IP
RegexClass::RegularExpression re;
CString csRegExp = (CString)"^([0-9]|[0-9][0-9]|[01][0-9][0-9]|2[0-4][0-9]|25[0-5])"
     + (CString)"(\\.([0-9]|[0-9][0-9]|[01][0-9][0-9]|2[0-4][0-9]|25[0-5])){3}";
re.SetExpression(csRegExp.GetBuffer());
re.StringToMatch = csIP.GetBuffer();
if (!re.MatchAll())
{
   bError = true;
   WLog("ini IP error - bad address" + csIP);
   exit(1);
}
//выделение байт из IP
int iStringIdx;
int iByteIdx = 0;
csIP.Trim();
CString csByte;
while (!csIP.IsEmpty())
{
   iStringIdx = csIP.Find('.');
   if (-1 != iStringIdx)
   {
     csByte = csIP.Mid(0, iStringIdx);
     addr[iByteIdx] = atoi (csByte);
     csIP.Delete(0, iStringIdx+1);
   }
   else
   {
     addr[iByteIdx] = atoi (csIP);
     csIP = "";
   }
   csIP.Trim();
   iByteIdx++;
}/* while */
}
8. Преобразование из строки в дату.
void TEXTtoOleDateTime (CString csDate, COleDateTime* datetime)
{
   RegexClass::RegularExpression re;
   CString csRegExp = (CString)"^\\d{1,2}([-. /])\\d{1,2}\\1\\d{2,4}";
   re.SetExpression(csRegExp.GetBuffer());
   re.StringToMatch = csDate.GetBuffer();
   if (!re.MatchAll())
   {
     bError = true;
     WLog("ini IP error - bad date" + csDate);
     exit(1);
   }
   int iStringIdx;
   byte iParam = 0;
   int iYear, iMonth, iDay;
   csDate.Trim();
   CString csParam;
   while (!csDate.IsEmpty())
   {
     iStringIdx = csDate.Find('.');
     if (-1 != iStringIdx)
     {
         csParam = csDate.Mid(0, iStringIdx);
         csDate.Delete(0, iStringIdx+1);
     }
     else
     {
         csParam = csDate;
         csDate = "";
     }
     switch (iParam)
     {
     case 0:
         iDay = atoi (csParam);
         break;
     case 1:
         iMonth = atoi (csParam);
         break;
     case 2:
         iYear = atoi (csParam);
         break;
     }
     csDate.Trim();
     iParam++;
   }
   datetime->SetDate(iYear, iMonth, iDay);
}
9. Разбиение строки на части (по разделителю - пробелу). В этом примере на входе строка csLine, на выходе части помещаются в вектор out. Части извлекаются с помощью итератора it, первая часть помещается в csPart.
#include <vector>
 ...
CString csLine = "part1 part2 part 3";
CString csPart;
CString csRegExp = "\\s+";
RegexClass::RegularExpression re;
std::vector<std::string> out;
re.SetExpression(csRegExp.GetBuffer());
re.StringToMatch = csLine.GetBuffer();
re.Split(out);
std::vector<std::string>::iterator it = out.begin();
csPart = it->c_str();

49. Ошибка линкера LNK2005
Linker Tools Error LNK2005 "symbol already defined in object" быстро устраняется добавлением опции /FORCE:MULTIPLE
в командную строку линкера: Configuration Properties\Linker\Command Line\Additional Options:\ добавляем /FORCE:MULTIPLE.
После компиляции получаем кучу страшных предупреждений, но программа работает:
Compiling resources...
Linking...
nafxcw.lib(afxmem.obj) : warning LNK4006: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) already defined in libcpmt.lib(newop.obj); second definition ignored
nafxcw.lib(afxmem.obj) : warning LNK4006: "void * __cdecl operator new[](unsigned int)" (??_U@YAPAXI@Z) already defined in libcpmt.lib(newaop.obj); second definition ignored
Release/autoname.exe : warning LNK4088: image being generated due to /FORCE option; image may not run
LINK : warning LNK4089: all references to 'SHELL32.dll' discarded by /OPT:REF
LINK : warning LNK4089: all references to 'comdlg32.dll' discarded by /OPT:REF
LINK : warning LNK4089: all references to 'ole32.dll' discarded by /OPT:REF

Build log was saved at "file://c:\VisualStudioProjects\autoname\Release\BuildLog.htm"
autoname - 0 error(s), 6 warning(s)
---------------------- Done ----------------------
   Rebuild All: 1 succeeded, 0 failed, 0 skipped
--------------------------------------------------------------------------------

50. Как обновлять окно при программном изменении внешнего вида.
varOutputList.SetWindowText(csFN + ", ["+ csIP +"]");
UpdateWindow();

51. Как обрабатывать нажатия кнопок (сообщения WM_KEYDOWN) в MFC CDialog.
Если Вы создали приложение с помощью визарда как диалоговое окно, и добавили обработчики сообщений для диалогового окна WM_KEYDOWN или WM_KEYUP (напомню, что это делается через Class View\правая кнопка на классе диалогового окна, Properties\кнопка Messages, далее выбираем сообщение и добавляем для него обработчик), то этот обработчик все равно срабатывать не будет. Проблема (и её решение) описана тут -
http://www.winterdom.com/dev/mfc/pretrans.html .
Проблема решается так:
1. В заголовочном файле класса (имя_класса.h), в секции public класса диалогового окна добавляется строка с переназначением процедуры PreTranslateMessage:
...
DECLARE_MESSAGE_MAP()
public:
...
BOOL PreTranslateMessage(MSG* pMsg);
};
2. В файле класса диалогового окна (имя_класса.cpp) вставляется код процедуры PreTranslateMessage (в этом примере отслеживается нажатие кнопок Ctrl+C):
BOOL CunusedportsDlg::PreTranslateMessage(MSG* pMsg)
{
   if (pMsg->message == WM_KEYDOWN)
   {
     if ((pMsg->wParam == 17) && (pMsg->lParam == 1900545))
         //Ctrl нажата
         bCtrl = true;
     else if (bCtrl && (pMsg->wParam == 67))
     {
         //нажата комбинация Ctrl+C
         Beep (2000, 100);
         bStop = true;
     }
   }
   if (pMsg->message == WM_KEYUP)
   {
     if ((pMsg->wParam == 17) && (pMsg->lParam == -1071841279))
         //Ctrl отпущена
         bCtrl = false;
     else if (bCtrl && (pMsg->wParam == 67))
         //отпущена комбинация Ctrl+C
         Beep (1000, 100);
   }
   return CDialog::PreTranslateMessage(pMsg);
}
Этот метод позволяет вставлять в процедуру PreTranslateMessage обработку и других сообщений.

52. Как добавлять хинты (hints, всплывающие подсказки) в приложение MFC
Почитать про это можно тут:
http://www.rsdn.ru/article/controls/tiptoe.xml
  и тут:
http://www.rsdn.ru/article/qna/ui/dlgtips.xml

Общее описание метода:
1. Добавить private или protected переменную типа CToolTipCtrl в класс вашего диалога.
2. Добавить в класс управляющую переменную (control member variable), для каждого элемента, у которого будет подсказка. Это можно сделать с помощью ClassWizard (на закладке Member Variable).
3. Переопределить CDialog::OnInitDialog и вызвать в нем CToolTipCtrl::Create. Затем вызвать CToolTipCtrl::AddTool для каждого элемента с подсказкой, передавая адрес управляющей переменной и текст подсказки в качестве параметров.
4. Переопределить CDialog::PreTranslateMessage и вызвать в ней CToolTipCtrl::RelayEvent для каждого сообщения, передаваемого в функцию. Это нужно для того, чтобы элемент ToolTip получал все необходимые сообщения мыши.

Практическая реализация по шагам (на примере простейшего приложения на основе стандартного диалога):
1a. Открываем файл имя_класса_диалога.h (у меня был файл unusedportsDlg.h), в определение класса (у меня был класс class CunusedportsDlg : public CDialog), в раздел public, в конец вставляем переменную класса типа CToolTipCtrl:
class CunusedportsDlg : public CDialog
{
  ...
protected:
  ...
public:
  ...
  afx_msg void OnClose();
  CToolTipCtrl hints;
};
1b. Еще добавляем переопределение в классе CDialog процедуры PreTranslateMessage:
class CunusedportsDlg : public CDialog
{
  ...
protected:
  ...
public:
  ...
BOOL PreTranslateMessage(MSG* pMsg);
  afx_msg void OnClose();
  CToolTipCtrl hints;
};
1c. Добавляем в имя_класса_диалога.cpp (у меня был файл unusedportsDlg.cpp), добавляем код процедуры PreTranslateMessage:
BOOL CunusedportsDlg::PreTranslateMessage(MSG* pMsg)
{
   // TODO: Add your specialized code here and/or call the base class
   if (pMsg->message == WM_KEYDOWN)
   {
     ...
   }
   if (pMsg->message == WM_KEYUP)
   {
     ...
   }
   hints.RelayEvent(pMsg);
   return CDialog::PreTranslateMessage(pMsg);
}
2. Идем на закладку Resource View, далее выбираем в дереве проекта папку Dialog, двойным щелчком открываем редактор диалога, правой кнопкой на каждом элементе диалога добавляем переменную (в контекстном меню выбираем Add Variable..., далее запускается Wizard, указываем в поле Variable name: что-то типа varOkBtn, жмем Finish). Так надо поступить с каждом элементом управления, для которого будет всплывающая подсказка (для кнопки, строки редактирования, пикера даты и т. п.).
3. В файле имя_класса_диалога.cpp (у меня был файл unusedportsDlg.cpp), в подпрограмме имя_класса::OnInitDialog() (у меня была подпрограмма BOOL CunusedportsDlg::OnInitDialog()) добавляем инициализацию хинтов, код должен быть наподобие такого:
...
//включаем систему подсказок
hints.Create(pDlg);
hints.AddTool(&varDateBeg, "select START data to analize free ports");
hints.AddTool(&varDateEnd, "select END data to analize free ports");
hints.AddTool(&varOutputList, "analize result will be here");
hints.AddTool(&varOkBtn, "click to start analize free ports");
...
   Теперь при запуске приложения у нас нужные элементы управления получат нужные надписи подсказок.
4. В процедуре имя_класса::PreTranslateMessage (у меня была процедура BOOL CunusedportsDlg::PreTranslateMessage(MSG* pMsg)) добавляем в конец код:
BOOL CunusedportsDlg::PreTranslateMessage(MSG* pMsg)
{
   ...
   hints.RelayEvent(pMsg);
   return CDialog::PreTranslateMessage(pMsg);
}
   Все, теперь при наведении курсора на элемент управления будут всплывать желтенькие подсказки.
   Если нужно переопределить эти подсказки runtime, то нужно пользоваться методом DelTool:
   ...
   hints.DelTool(&varOkBtn);
   hints.AddTool(&varOkBtn, "новый текст хинта");
   ...

53. Особенности подпрограмм чтения файлов.
Для работы с файлами есть два основных метода - _open/_filelength/_read/_close, и fopen/fread/feof/fclose. Опишем оба метода, их достоинства и недостатки.
1. _open/_filelength/_read/_close
   Работа с файлами через хендл. Самая большая неприятность этого метода - файл всегда открывается в текстовом режиме. Это означает, что часть информации будет при чтении потеряна - например, последовательность \r\n преобразуется в \n, и это не всегда удобно. Удобство в том, что есть подпрограмма определения размера файла (_filelength). Пример использования:

int fhSour;
int iFileLength, iBytesReaded;
char *buf;
fhSour = _open(catalog + "\\" + csFN, _O_RDONLY);
if (-1 == fhSour)
{
   WLog( "The file " + catalog + "\\" + csFN + " was not opened" );
   exit (1);
}
iFileLength = _filelength(fhSour);
buf = (char*)malloc(iFileLength);
//iButesReaded может быть меньше iFileLength
iBytesReaded = _read(fhSour, buf, iFileLength);
_close(fhSour);
if (-1 == iBytesReaded)
{
   WLog("Error read file " + csFN);
   exit (1);
}
//делаем ASCIIZ-строку
buf[iBytesReaded] = 0;
CString csFileContent = (CString)buf;
free(buf);

2. fopen/fread/feof/fclose
   Работа с файлами через потоки, хороший выбор. Удобство в том, что можно открывать файлы в двоичном виде. Неудобство в том, что нельзя сразу определить размер файла, не прочитав его до конца (конец файла тестируется feof). Пример использования:
FILE *stream;
size_t count, total = 0;
char buffer[100];
CString csOutput = "";
if( (stream  = fopen( csFileName, "rb" )) == NULL )
   WLog( "Error open (not exist?): " + csFileName );
else
{
   while( !feof( stream ) )
   {
     /* Attempt to read in 99 bytes: */
     count = fread( buffer, sizeof( char ), 99, stream );
     if( ferror( stream ) )
     {
         WLog( "Error read file " + csFileName);
         break;
     }
     buffer[count] = 0;
     total += count;
     csOutput += (CString)buffer;
   }
   fclose( stream );
   varOutputList.SetWindowText(csOutput);
}

54. CR=='\r', LF=='n'
Постоянно забываю эти простые специальные символы.
CR,  Carriage Return, код 0x0D, использование в C как
\r
LF,  Line Feed, код 0x0A, использование в C как
\n
CRLF, \r\n, используется в файлах DOS и Windows как последовательность, завершающая строку (0x0D 0x0A).

55. Как на кнопке CButton поместить картинку.
- Resource View\папка Bitmap\правая кнопка Add Resource...\выбираем Bitmap\нажимаем кнопку New
- в папке ресурсов Bitmap появляется новая запись IDB_BITMAP1 (это ID картинки) и одновременно
  открывается окно редактора картинки. IDB_BITMAP1 лучше поменять на что-то более подходящее
  по смыслу, например IDB_MYBUTTON (делается через свойства картинки, параметр ID).
- меняем у нашей кнопки свойство Bitmap на True, а свойство Caption очищаем (свойство Caption
  очищать не обязательно, все равно картинка закроет надпись на кнопке).
- рисуем картинку. Проще всего открыть какой-нибудь ресурс в Интернете, посвященный иконкам,
  например http://www.iconsfree.org/free-icons/language/eng/c/categoriesRating/icons-categories-rating.html,
  выбрать нужную иконку и прямо с экрана скопировать в буфер обмена растр картинки (для этого
  очень рекомендую программу Kleptomania - копирует с экрана и графику, и текст), а затем
  вставить содержимое буфера прямо в редакторе ресурсов Visual Studio.
- к кнопке, в которую будем вставлять картинку, привязываем переменную. Для этого на кнопке
  в контекстном меню выбираем Add Variable..., в окно Variable name: подставляем что-то типа varMyBtn
- в процедуре инициализации формы диалога OnInitDialog() добавляем следующий код (процедура WLog
  пишет ошибки в текстовый файл):
HBITMAP bmMyBtn;
bmMyBtn = LoadBitmap(theApp.m_hInstance, MAKEINTRESOURCE(IDB_MYBUTTON));
CString csMsg;
char* lpMsgBuf;
if (NULL == bmMyBtn)
{
   FormatMessage(
           FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
           NULL,
           GetLastError(),
           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
           (LPTSTR) &lpMsgBuf,
           0,
           NULL);
   csMsg = (char*)lpMsgBuf;
   csMsg.Trim();
   WLog("error load bitmap for button: " + csMsg);
}
varMyBtn.SetBitmap( bmMyBtn );

56. Как запустить notepad из кода (пример откроет notepad с файлом boot.ini).
CString csFileName = "C:\\boot.ini";
ShellExecute(NULL, "open", "notepad.exe", csFileName, NULL, SW_SHOWNORMAL);

57. Как добавить поддержку zip-архивов.
- скачиваем ZipArchive Library с сайта
http://www.artpol-software.com/Download.aspx, это 2 файла - ziparchive.zip (Sources, Documentation and Samples) и setup.zip (Installation Setup).
- запускаем файл setup.exe (из архива setup.zip), указываем директорию для установки C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library. Если у Вас не MSDN версии 6, то галочку "интегрировать help" не ставим.
- теперь нам надо получить откомпилированную библиотеку ZipArchive.lib той версии, которая подходит к нашей версии Visual Studio 7.0. Для этого из архива ziparchive.zip, который мы скачали, распаковываем 2 файла ZipArchive.vcproj и ZipArchive.sln в папку C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive\. Те файлы, что там есть (версии 8.0), можно спокойно стереть, поскольку в ziparchive.zip есть их копии в папке _projects\Visual Studio 2005\ZipArchive\. Двойным щелчком открываем файл C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive\ZipArchive.vcproj, выбираем конфигурацию Release, компилируем. В результате получаем файл C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive\Release\ZipArchive.lib.
- создаем новый проект в Visual Studio - Visual C++ Projects\Win32\Win32 Console Project, поддержку ATL и MFC не добавляем.
- в свойствах проекта, раздел C/C++\Additional Include Directories добавляем путь к папке, где лежат h-файлы:
  "C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive"
- в свойствах проекта, раздел Linker\Additional Library Directories добавляем путь к папке, где лежит откомпилированная библиотека ZipArchive.lib:
  "C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive\Release"
- в свойствах проекта, в раздел Linker\Input\Additional Dependencies добавляем ZipArchive.lib.

58. Как получить доступ к командной строке в MFC-диалоге на основе CDialog.
theApp.m_lpCmdLine - указатель на командную строку, в которой только опции, разделенные пробелами (без имени приложения).
MessageBox(theApp.m_lpCmdLine, "", MB_OK);

59. Как программно завершить приложение на основе CDialog
exit(код_возврата)
или
CDialog* pDlg;
pDlg = (CDialog*)theApp.m_pMainWnd;
...
pDlg->EndDialog(код_возврата);

60. Prof-UIS Freeware v.2.64 - профессиональная библиотека для пользовательского интерфейса. Библиотека позволяет делать красивый современный дизайн для проекта, добавляет новые возможности.
- официальный сайт -
http://www.prof-uis.com/, скачать отсюда - http://www.prof-uis.com/download/profuis264_freeware.zip , документация здесь - http://www.prof-uis.com/download/help/profuishelp.zip (если скачали profuis264_freeware.zip, то доку можно не скачивать), дока по настройке (Getting started with Prof-UIS) здесь - http://www.prof-uis.com/ArticleRead.aspx?AID=220
- из архива profuis264_freeware.zip распаковать каталог Prof-UIS в каталог c:\Program Files\Microsoft Visual Studio .NET 2003\
- как настраивать тут -
http://www.prof-uis.com/FAQView.aspx?CID=101
- внутри Visual Studio идем Tools\Options...\Projects\VC++ Directories, Show directories for настраиваем по таблице (здесь $(VCInstallDir) равно c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7):
Include files    .../Prof-UIS/Include или $(VCInstallDir)\..\Prof-UIS\Include
                 .../Prof-UIS/Src     или $(VCInstallDir)\..\Prof-UIS\Src
Source files     .../Prof-UIS/Include или $(VCInstallDir)\..\Prof-UIS\Include
                 .../Prof-UIS/Src     или $(VCInstallDir)\..\Prof-UIS\Src
Library files    .../Prof-UIS/Bin_710 или $(VCInstallDir)\..\Prof-UIS\Bin_710
- скачать Prof-UIS Application Wizard отсюда - http://www.prof-uis.com/download/wizards/ProfUisAppWizard_2003.ZIP (страница закачки http://www.prof-uis.com/FAQView.aspx?CID=101 )
- распаковать в папку c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\VCWizards\ папку ProfUISAppWizard из архива ProfUisAppWizard_2003.ZIP
- файлы ProfUISAppWizard.ico, ProfUISAppWizard.vsdir и ProfUISAppWizard.vsz из архива ProfUisAppWizard_2003.ZIP положить в папку c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\vcprojects\
- теперь сразу, без перегрузки Visual Studio будет доступен новый визард по адресу File\New\Project...\Visual C++ Projects\ProfUISAppWizard
- нужно откомпилировать все версии библиотеки ProfUIS264*.lib, для этого откройте c:\Program Files\Microsoft Visual Studio .NET 2003\Prof-UIS\Workspace\ProfUIS_710.sln, выберите проект ProfUISLIB и откомпилируйте его со всеми возможными конфигурациями:
ANSI Debug, Static ANSI Debug
ANSI Debug RDE, Static ANSI Debug RDE
ANSI Debug RDE with MFC DLL, Static ANSI Debug RDE with MFC DLL
ANSI Debug with MFC DLL, Static ANSI Debug with MFC DLL
ANSI Release, Static ANSI Release
ANSI Release RDE, Static ANSI Release RDE
ANSI Release RDE with MFC DLL, Static ANSI Release RDE with MFC DLL
ANSI Release with MFC DLL, Static ANSI Release with MFC DLL
MBCS Debug, Static MBCS Debug
MBCS Debug RDE, Static MBCS Debug RDE
MBCS Debug RDE with MFC DLL, Static MBCS Debug RDE with MFC DLL
MBCS Debug with MFC DLL, Static MBCS Debug with MFC DLL
MBCS Release, Static MBCS Release
MBCS Release RDE, Static MBCS Release RDE
MBCS Release RDE with MFC DLL, Static MBCS Release RDE with MFC DLL
MBCS Release with MFC DLL, Static MBCS Release with MFC DLL
Unicode Debug, Static Unicode Debug
Unicode Debug RDE, Static Unicode Debug RDE
Unicode Debug RDE with MFC DLL, Static Unicode Debug RDE with MFC DLL
Unicode Debug with MFC DLL, Static Unicode Debug with MFC DLL
Unicode Release, Static Unicode Release
Unicode Release RDE, Static Unicode Release RDE
Unicode Release RDE with MFC DLL, Static Unicode Release RDE with MFC DLL
Unicode Release with MFC DLL, Static Unicode Release with MFC DLL
  Для этого идем в Build\Batch Build..., ставим галки на конфигурациях ProfUISLIB, жмем Build.
  После того, как все выбранные конфигурации откомпилируются (это надолго, можно пойти попить кофейку), в каталоге c:\Program Files\Microsoft Visual Studio .NET 2003\Prof-UIS\Bin_710\ появится куча файлов ProfUIS264*.lib.
- для того, чтобы отлаживаемые программы запускались, нужно добавить в переменную Path системы путь до библиотек c:\Program Files\Microsoft Visual Studio .NET 2003\Prof-UIS\Bin_710\, для этого открываем Control Panel\System\Advanced\Environment Variables...\System variables, выбираем переменную Path и нажимаем кнопку Edit..., в строке Variable Value: в конец добавляем ;c:\Program Files\Microsoft Visual Studio .NET 2003\Prof-UIS\Bin_710\
  Перелогиниваться при этом не нужно, просто надо перезапустить приложение (Visual Studio), из которого запускаем программу. Другой способ - скопировать нужную dll в каталог отлаживаемой программы, или задать в свойствах проекта линковать библиотеки статически - в свойствах проекта программы, в разделе Configuration Properties\General указать Use of MFC в Use MFC in a Static Library.

-----------------------------------------------------------------------------------------
6 июня 2009
61. log.obj : error LNK2001: unresolved external symbol "class ATL::CStringT<char,class StrTraitMFC_DLL<char,class ATL::ChTraitsCRT<char> > > LogFileName" (?LogFileName@@3V?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@A)
.\Debug/UsbHidDemoCode.exe : fatal error LNK1120: 1 unresolved externals

Такая ошибка у меня часто возникает, когда я беру модуль *.cpp (из другого старого проекта) без хедера и пытаюсь вставить в новый проект. На переменные вставляемого модуля, объявленные с директивой extern (в этом примере так была объявлена переменная LogFileName) будет в этом случае будет происходить ошибка LNK2001 и следующая за ней общая фатальная ошибка LNK1120. Проблема устраняется объявлением CString LogFileName в любом другом модуле.

-----------------------------------------------------------------------------------------
21 июля 2011
62. Компиляция файла *.C как *.CPP - иногда это нужно сделать, если файл кода используется совместно с embedded-проектами для микроконтроллеров. Правой кнопкой щелкаем на нужном C-файле в дереве проекта, выбираем Properties -> Configuration Properties -> C/C++ -> Advanced -> Compile As -> Compile as C++ Code (/TP).
Последнее обновление ( 21.07.2011 )
 

Комментарии  

  1. #2 Ghost
    2010-02-1600:20:50 1 совет - тот-же эффект:
    #pragma comment(lib, "winmm.lib")
    17 совет - если в диалоговом окне несколько итемов с одинаковым ID=IDC_STATIC (а в VS это идентификатор по умолчанию для StaticText и пр.), то результат непредсказуем
  2. #1 Ravager
    2008-11-2615:35:46 Да, для работы с файлами можно пользоваться стандартными API. Функцией CreateFile открываем файл на чтение/запись, потом читаем/пишем с помощью ReadFile/WriteFile или маппим.

Добавить комментарий

:D:lol::-);-)8):-|:-*:oops::sad::cry::o:-?:-x:eek::zzz:P:roll::sigh:

Защитный код
Обновить

< Пред.   След. >

Top of Page
 
microsin © 2017