Сообщение о прокрутке в окне


Сообщение WM_HSCROLL приходит в окно диалога (читайте: объекту диалогового класса, связанного с окном) всякий раз, как пользователь изменяет положение одного из ползунков, расположенных на лице диалога. Это довольно удобно, так как мы можем в одной функции обработки (onHScroll) отследить изменения, произошедшие в любом из 11 регуляторов. Введите коды обработки этого сообщения, которые сходны с кодами, приведенными в приложении на основе MFC, за исключением СОМ-специфики общения между классами CPropDlg и COpenGL:
LRESULT CPropDlg::OnHScroll(UINT /*uMsg*/, WPARAM wParam,
LPARAM iParam, BOOL& /*bHandled*/)
{
//====== Информация о событии запакована в wParara
int nCode = LOWORD(wParam), nPos = HIWORD(wParam), delta, newPos;
HWND hwnd = (HWND) IParam;
// Выясняем номер и идентификатор активного ползунка
UINT nID;
int num = GetSliderNum(hwnd, nID);
//====== Выясняем суть события
switch (nCode)
{
case SB_THUMBTRACK:
case SBJTHUMBPOSITION:
m_Pos[num] = nPos;
break;
//====== Сдвиг до упора влево (клавиша Home)
case SB_LEFT:
delta = -100;
goto New_Pos;
//====== Сдвиг до упора вправо (клавиша End)
case SB_RIGHT:
delta = + 100;
goto New_Pos;
case SB_LINELEFT:
// И т.д.
delta = -1;
goto New_Pos;
case SB_LINERIGHT:
delta = +1;
goto New_Pos;
case SB_PAGELEFT:
delta = -20;
goto New_Pos;
case SB_PAGERIGHT:
delta = +20;
goto New_Pos;
New_Pos:
newPos = m_Pos[num] + delta;
m_Pos[num] = newPos<0 ? 0
: newPos>100 ? 100 : newPos;
break;
case SB_ENDSCROLL: default:
return 0;
}
//=== Готовим текстовое выражение позиции ползунка
char s[8];
sprintf (s,"%d",m_Pos[num]);
SetDlgltemText(nID, (LPCTSTR)s);
//====== Цикл пробега по всем объектам типа PropDlg
for (UINT i = 0; i < m_nObjects; )
//====== Добываем интеофейсн:
//====== Добываем интерфейсный указатель
CComQIPtr<IOpenGL, &IID_IOpenGL> p (m_ppUnk[i] ) ;
//====== Устанавливаем конкретный параметр
if FAILED (p->SetLightParam (num, m_Pos [num] ) )
ShowError();
return 0;
}
}
return 0;
}
В данный момент вы можете проверить функционирование регуляторов в суровых условиях СОМ. Они должны работать.
Реакция на выбор в окне выпадающего списка
Теперь введем реакцию на выбор пользователем новой строки в окне выпадающего списка. Для этого выполните следующие действия:

  1. Откройте в окне редактора Studio.Net шаблон окна диалога IDD_PROPDLG.
  2. Поставьте фокус в окно выпадающего списка IDC_FILLMODE и переведите фокус окно Properties.
  3. Нажмите кнопку Control Events, расположенную на инструментальной панели окна Properties.
  4. Найдите строку с идентификатором уведомляющего сообщения CBN_SELCHANGE и в ячейке справа выберите действие <Add>, для того чтобы там появилось имя функции обработки OnSelchangeFillmode.
  5. Перейдите в окно PropDlg.cpp и введите следующие коды в заготовку функции OnSelchangeFillmode.

LRESULT CPropDlg
::OnSelchangeFillmode(WORD/*wNotifyCode*/, WORD /*wID*/,
HWND hWndCtl, BOOL& bHandled)
{
//====== Цикл пробега по всем объектам типа PropDlg
for (UINT i = 0; i < m_nObjects; i++)
{
CComQIPtr<IOpenGL, &IID_IOpenGL> p(m_ppUnk[i]);
// Выясняем индекс строки, выбранной в окне списка
DWORD sel = (DWORD)SendMessage(hWndCtl, CB_GETCURSEL,0,0);
// Преобразуем индекс в режим отображения полигонов
sel = sel==0 ? GL_POINT
: sel==l ? GL_LINE : GL_FILL;
//====== Устанавливаем режим в классе COpenGL
if FAILED (p->SetFillMode(sel))
{
ShowError();
return 0;
}
}
bHandled = TRUE;
return 0;
}
Обратите внимание на то, что нам пришлось убирать два комментария, чтобы сделать видимым параметры hWndCtl и bHandled.
Реакция на нажатия кнопок
При создании отклика на выбор режима изображения полигонов следует учесть попеременное изменение текста и состояния кнопки. Поставьте курсор на кнопку IDC_QUADS и в окне Properties нажмите кнопку Control Events. Затем найдите строку с идентификатором уведомляющего сообщения BN_CLICKED и в ячейке справа выберите действие <Add>. Текст в ячейке должен измениться и стать OnClickedQuads. Введите следующие коды в заготовку функции:
LRESULT CPropDlg::OnClickedQuads(WORD /*wNotifyCode*/,
WORD /*wID*/, HWND /*hWndCtl*/, BOOL& bHandled)
{
//====== По всем объектам PropDlg
for (UINT i = 0; i < m_nObjects; i++)
{
//====== Добываем интерфейсный указатель
CComQIPtr<IOpenGL, &IID_IOpenGL> p(m_ppUnk[i]) ;
//====== Переключаем режим
m_bQuad = !m_bQuad;
//====== Устанавливаем текст на кнопке
SetDlgltemText(IDC_QUADS, m_bQuad ? "Quads" : "Strip");
if FAILED (p->SetQuad(m_bQuad))
{
ShowError();
return 0;
bHandled = TRUE;
return 0;
}
Аналогичные, но более простые действия следует произвести в реакции на нажатие кнопки Выбор файла. Введите функцию для обработки этого события и вставьте в нее следующий код:
LRESULT CPropDlg: rOnCl'ickedFilename (WORD /*wNotif yCode*/,
WORD /*wID*/, HWND /*hWndCtl*/, BOOL& bHandled)
{
for (UINT i = 0; i < m_nObjects; i++)
{
CComQIPtr<IOpenGL, &IID_IOpenGL> p (m_ppUnk [i] ) ;
//====== Вызываем функцию класса COpenGL
if FAILED (p->ReadData() )
{
ShowError () ;
return 0 ;
}
bHandled = TRUE;
return 0;
}
Постройте сервер и проверьте работу страницы свойств. Попробуйте прочесть другой файл, например тот, который был создан приложением, созданным в рамках MFC. Так как мы не изменяли формат данных, записываемых в файл, то все старые файлы должны читаться.
Управление объектом с помощью мыши
Алгоритм управления ориентацией объекта с помощью мыши мы разработали ранее. Вы помните, что перемещение курсора мыши при нажатой кнопке должно вращать изображение, причем горизонтальное перемещение вращает его вокруг вертикальной оси Y, а вертикальное — вокруг горизонтальной оси X. Если одновременно с мышью нажата клавиша Ctrl, то объект перемещается (glTranslatef) вдоль осей X и Y. Наконец, с помощью правой кнопки изображение перемещается вдоль оси Z, то есть приближается или отдаляется. Таймер помогает нам в том, что продолжает вращение, если очередной квант перемещения мышью стал выше порога чувствительности. Скорость вращения имеет два пространственных компонента, которые пропорциональны разности двух последовательных во времени координат курсора. Чем быстрее движется курсор при нажатой левой кнопке, тем большая разность координат будет обнаружена в обработчике сообщения WM_MOUSEMOVE. Именно в этой функции оценивается желаемая скорость вращения.
Описанный алгоритм обеспечивает гибкое и довольно естественное управление ориентацией объекта, но, как вы помните, он имеет недостаток, который проявляется, когда модуль угла поворота вдоль первой из вращаемых (с помощью glRotate) осей, в нашем случае — это ось X, превышает 90 градусов. Вам, читатель, я рекомендовал самостоятельно решить эту проблему и устранить недостаток. Ниже приводится одно из возможных решений. Если вы, читатель, найдете более изящное, буду рад получить его от вас. Для начала следует ввести в состав класса COpenGL функцию нормировки углов вращения, которая, учитывая периодичность процесса, ограничивает их так, чтобы они не выходили из диапазона (-360°, 360°):
void COpenGL::LimitAngles()
{
//====== Нормирование углов поворота так,
//====== чтобы они были в диапазоне (-360°, +360°)
while (m_AngleX < -360.f)
m_AngleX += 360.f;
while (m_AngleX > 360.f)
m_AngleX -= 360.f;
while (m_AngleY < -360.f)
m_AngleY += 360.f;
while (m_AngleY > 360.f)
m_AngleY -= 360.f;
}
Затем следует вставить вызовы этой функции в те точки программы, где изменяются значения углов. Кроме того, надо менять знак приращение m_dx, если абсолютная величина угла m_AngleX попадает в диапазон (90°, 270°). Это надо делать при обработке сообщения WM_MOUSEMOVE. Ниже приведена новая версия функции обработки этого сообщения, а также сообщения WM_TIMER, в которое также следует ввести вызов функции нормировки:
LRESULT COpenGL::OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM IParam, BOOL& bHandled)
{
//====== Если был захват
if (m_bCaptured)
{
//====== Вычисляем желаемую скорость вращения
short xPos = (short)LOWORD(IParam);
short yPos = (short)HIWORD(1Param);
m_dy = float(yPos - m_yPos)/20.f;
m_dx = float(xPos - m_xPos)/20.f;
//====== Если одновременно была нажата Ctrl,
if (wParam & MK_CONTROL)
{
//=== Изменяем коэффициенты сдвига изображения
m_xTrans += m_dx;
m_yTrans -= m_dy;
}
else
{
//====== Если была нажата правая кнопка
if (m_bRightButton)
//====== Усредняем величину сдвига
m_zTrans += (m_dx + m_dy)/2.f;
else
{
//====== Иначе, изменяем углы поворота
//====== Сначала нормируем оба угла
LiraitAngles();
//=== Затем вычисляем модуль одного из них
double a = fabs(m_AngleX);
// и изменяем знак приращения(если надо)
if (90. < а && а < 270.) m_dx = -m_dx;
m_AngleX += m_dy;
m_AngleY += m_dx;
}
}
// В любом случае запоминаем новое положение мыши
m_xPos = xPos;
m_yPos = yPos;
FireViewChange();
}
bHandled = TRUE; return 0;
}
LRESULT COpenGL: :OnTimer (UINT /*uMsg*/, WPARAM
/*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
//====== Нормировка углов поворота

LimitAngles () ;
//====== Увеличиваем эти углы
m_AngleX += m_dy; m_AngleY += m_dx;
//====== Просим перерисовать окно
FireViewChange();
bHandled = TRUE;
return 0;
}
Ниже приведены функции обработки других сообщений мыши. Они сходны с теми, которые мы разработали для MFC-приложения, за исключением прототипов и возвращаемых значений. Начнем с обработки нажатия левой кнопки. Оно, очевидно, должно всегда останавливать таймер, запоминать факт нажатия кнопки и текущие координаты курсора мыши:
LRESULT COpenGL::OnLButtonDown(UINT /*uMsg*/, WPARAM
/*wParam*/, LPARAM IParam, BOOL& bHandled)
{
//====== Останавливаем таймер
KillTimer(1);
//====== Обнуляем кванты перемещения
m_dx = O.f;
m_dy = 0.f;
//====== Захватываем сообщения мыши,
//====== направляя их в свое окно
SetCapture();
//====== Запоминаем факт захвата
m_bCaptured = true;
//====== Запоминаем координаты курсора
m_xPos = (short)LOWORD(IParam);
m_yPos = (short)HIWORD(IParam);
bHandled = TRUE; return 0;
}
В обработчик отпускания левой кнопки вводится анализ на необходимость продолжения вращения с помощью таймера. В случае превышения порога чувствительности, следует запустить таймер, который продолжает вращение, поддерживая текущее значение его скорости. Любопытно, что в алгоритме нам не понадобился ни один их входных параметров функции:
LRESULT COpenGL::OnLButtonUp(UINT /*uMsg*/, WPARAM
/*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
//====== Если был захват,
if (m_bCaptured)
{
//=== то анализируем желаемый квант перемещения
//=== на превышение порога чувствительности
if (fabs(m_dx) > 0.5f || fabs(m_dy) > 0.5f)
//====== Включаем режим постоянного вращения
SetTimer(1,33) ;
else
//====== Выключаем режим постоянного вращения
KillTimer(1);
//====== Снимаем флаг захвата мыши
m_bCaptured = false;
//====== Отпускаем сообщения мыши
ReleaseCapture();
}
bHandled = TRUE;
return 0;
}
При нажатии на правую кнопку выполняются те же действия, что и при нажатии на левую, но дополнительно запоминается факт нажатия правой кнопки, с тем чтобы можно было правильно интерпретировать последующие сообщения о перемещении указателя мыши и вместо вращения изображения производить его сдвиг вдоль оси Z. Отметьте тот факт, что мы должны убрать символы комментариев вокруг параметров:
LRESULT COpenGL::OnRButtonDown(UINT uMsg, WPARAM wParam,
LPARAM IParam, BOOL& bHandled)
{
//====== Запоминаем факт нажатия правой кнопки
m_bRightButton = true;
//====== Воспроизводим реакцию на левую кнопку
OnLButtonDown(uMsg, wParam, IParam, bHandled);
return 0;
}
Отпускание правой кнопки просто отмечает факт прекращения перемещения вдоль оси Z и отпускает сообщения мыши (ReleaseCapture), для того чтобы они могли правильно обрабатываться другими окнами, в том числе и нашим окном-рамкой. Если этого не сделать, то будет невозможно использоваться меню:
LRESULT COpenGL::OnRButtonUp(UINT /*uMsg*/, WPARAM
/*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
m_bRightButton = false;
m_bCaptured = false;
ReleaseCapture();
bHandled = TRUE;
return 0;
}
Запустите и проверьте управляемость объекта. Введите коррективы чувствительности мыши. В заключение отметим, что при выборе параметров заготовки ATL мы могли на вкладке Miscellaneous (Разное) поднять не только флажок Insertable, но и windowed Only. Это действие сэкономило бы те усилия, которые были потрачены на поиск неполадок, вызванных отсутствием флага m bWindowOnly.
  
Создание контейнера на базе MFC
До сих пор для отладки и демонстрации нашего ActiveX-элемента мы пользовались услугами тестового контейнера ActiveX Control Test Container,который входит в состав инструментов Studio.Net. Пришла пора показать, как с помощью библиотеки классов MFC можно создать свой собственный простой контейнер, специализированный для управления элементом OpenGL Class.

  1. Создайте новый основанный на диалоге MFC-проект и назовите его TestGL.
  2. В окне вновь созданного шаблона диалога IDD_TESTGL_DIALOG вызовите контекстное меню и выберите команду Insert ActiveX Control.
  3. В окне появившегося диалога Insert ActiveX Control найдите строку OpenGL Class и нажмите ОК.
  4. Вы должны увидеть рамку нового элемента, размещенного Studio.Net в форме диалога. Элементу присвоен идентификатор IDCJDPENGLI, который можно увидеть в окне Properties. Уберите из него завершающую единицу.
  5. Растяните окно нового элемента так, чтобы оно занимало примерно 80% площади всего окна диалога (рис. 9.3).

В отличие от Visual Studio б в конце этой процедуры в состав проекта (по умолчанию) не будет включен новый класс-оболочка (wrapper class) под именем CGpenGL. Такой класс необходим для дальнейшей работы с внедренным элементом ActiveX.
В документации бета-версии Studio.Net я нашел лишь намек на то, что wrapper-класс может быть создан с помощью ClassWizard. Однако мне не удалось добиться этого. Поэтому мы создадим класс-оболочку вручную. Конечно, здесь я использую заготовку класса, полученную в рамках Visual Studio 6. Она оказалась вполне работоспособной и в новой Studio.Net. Будем надеяться, что в следующих версиях Studio.Net рассмотренный процесс автоматического создания класса будет достаточно прозрачен.
  
Класс-оболочка
Обычно при создании приложения-контейнера для элемента ActiveX придерживаются следующей стратегии:

  1. Вставляют уже зарегистрированный элемент ActiveX в форму приложения контейнера, используя так называемую галерею объектов (Gallery).
  2. В одном из классов контейнера определяют переменную того же типа, что и класс-оболочка для внедренного элемента.
  3. Программируют поведение элемента, управляя им с помощью этой переменной.

Первый шаг этого алгоритма вы уже выполнили, теперь введите в состав проекта два новых файла OpenGLh и OpenGLcpp, которые будут содержать коды класса-оболочки copenGL. Вот содержимое файла заголовков (OpenGLh):
#pragma once
//=========== COpenGL wrapper class
class COpenGL : public CWnd
{
protected:
DECLARE_DYNCREATE(COpenGL)
public:
//==== Метод для добывания CLSID нашего элемента
CLSID const& GetClsidO
{
static CLSID const clsid =
{
0x519d9ed8, Oxbc4'6, 0x4367,
{ Ox9c, OxcO, 0x49, 0x81, 0x40, Oxf3, 0x94, 0x16 }
};
return clsid;
}
virtual BOOL Create(LPCTSTR IpszClassName,
LPCTSTR IpszWindowName, DWORD dwStyle,
const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL)
{
return CreateControl(GetClsid(), IpszWindowName,
dwStyle, rect, pParentWnd, nID)
}
BOOL Create (LPCTSTR IpszWindowName, DWORD dwStyle,
const RECT& rect, CWnd* pParentWnd, UINT nID, CFile* pPersist = NULL,
BOOL bStorage = FALSE, BSTR bstrLicKey = NULL)
{
return CreateControl(GetClsidO, IpszWindowName, dwStyle, rect, pParentWnd, nID, pPersist, bStorage, bstrLicKey);
}
//====== Методы, экспонируемые элементом ActiveX
public:
void SetFillColor(unsigned long newValue);
unsigned long GetFillColor();
void GetLightParams(long* pPos);
void SetLightParam(short Ip, long nPos);
void ReadData();
void SetFillMode(DWORD mode);
void GetFillMode(DWORD* pMode);
void GetQuad(BOOL* bQuad);
void SetQuad(BOOL bQuad);
};
Самым важным моментом в процедуре вставки класса является правильное задание CLSID того класса OpenGL, который был зарегистрирован в операционной системе при создании DLL-сервера, то есть нашего элемента ActiveX. He пытайтесь сравнивать те цифры, которые приведены в книге, с теми, которые были приведены в ней же до этого момента, так как в процессе отладки пришлось не раз менять как классы, так и целиком приложения. Мне не хочется отслеживать эти жуткие номера. Если вы хотите вставить правильные цифры, то должны взять их из вашей версии предыдущего приложения ATLGL. Например, откройте файл ATLGL.IDL и возьмите оттуда CLSID для ко-класса OpenGL, то есть найдите такой фрагмент этого файла:
[
uuid(519D9ED8-BC46-4367-9CCO-498140F39416),
helpstring("OpenGL Class") ]
coclass OpenGL
{
[default] interface IOpenGL;
[default, source] dispinterface _IOpenGLEvents;
};
И скопируйте первую строку
uuid(519D9ED8-BC46-4367-9CCO-498140F39416),
но с вашими цифрами и вставьте ее в качестве комментария в файл OpenGLh нового проекта TestGL. Затем аккуратно, соблюдая формат, принятый для структуры CLSID, перенесите только цифры в поля статической структуры clsid, которую вы видите в методе GetClsid класса-оболочки. Цифры должны быть взяты из принесенной строки, но их надо отформатировать (разбить) по-другому принципу. Например, для нашего случая правильным будет такое тело метода GetClsid:
CLSID const& GetClsid()
{
// Следующая строка взята из файла ATLGL.IDL
// 519D9ED8-BC46-4367-9CCO-498140F39416
static CLSID const clsid =
{
//======== Эти цифры взяты из файла ATLGL.IDL
0x519d9ed8, 0xbc46, 0x4367,
{ 0х9с, 0xc0, 0x49, 0x81, 0x40, 0xf3, 0x94, 0x16 } ) ;
return clsid;
}
Кроме этого важного фрагмента в новом классе объявлены два совмещенных метода Create, каждый из которых умеет создавать окно внедренного элемента ActiveX с учетом особенностей стиля окна (см. справку по CWnd: :CreateControl). Затем в классе-оболочке должны быть представлены суррогаты всех методов, экспонируемых классом OpenGL COM DLL-сервера ATLGL.DLL. В том, что вы не полностью приводите тела методов сервера, иначе это был бы абсурд, хотя и так близко к этому, можно убедиться, просмотрев на редкость унылые коды реализации класса-оболочки, которые необходимо вставить в файл OpenGLcpp. Утешает мысль, что в исправной Studio.Net эти коды не придется создавать и редактировать вручную:
#include "stdafx.h"
#include "opengl.h"
IMPLEMENT_DYNCREATE(COpenGL, CWnd)
//====== Стандартное свойство реализовано
//====== в виде пары методов Get/Set
void COpenGL::SetFillColor(unsigned long newValue)
{
static BYTE parms[] =
VTS_I4; InvokeHelper(0xfffffe02, DISPATCH_PROPERTYPUT,VT_EMPTY,
NULL, parms, newValue);
}
//====== Стандартное свойство
unsigned long COpenGL::GetFillColor0 {
unsigned long result;
InvokeHelper (Oxfffffe02, DISPATCH_PROPERTYGET, VT_I4, (void4)&result, NULL);
return result;
}
//====== Наши методы сервера
void COpenGL::GetLightParams(long* pPos)
{
static BYTE parms[] = VTS_PI4;
InvokeHelper (Oxl, DISPATCH_METHOD, VT_EMPTY, NULL,
parms, pPos);
}
void COpenGL: : SetLightParam (short lp, long nPos)
{
static BYTE parms [ ] = VTS 12 VTS 14;
InvokeHelper{0x2, DISPATCH_METHOD, VT_EMPTY, NULL,
parms, lp, nPos);
}
void COpenGL::ReadData()
InvokeHelper(0x3, DISPATCH_METHOD, VT_EMPTY, 0, 0) ;
void COpenGL::GetFillMode(DWORD* pMode)
static BYTE jparms[] =
VTS_PI4; InvokeHelper (0x4, DISPATCH_METHOD, VT_EMPTY, NULL,
parms, pMode);
}
void COpenGL::SetFillMode(DWORD nMode)
static BYTE parms[] =
VTS_I4;
InvokeHelper(0x5, DISPATCH_METHOD, VT_EMPTY, NULL, parms, nMode);
void COpenGL::GetQuad(BOOL* bQuad)
static BYTE parms[] =
VTS_PI4;
InvokeHelper(0x6, DISPATCH_METHOD, VT_EMPTY, NULL, parms, bQuad);
void COpenGL::SetQuad(BOOL bQuad)
static BYTE parms[] =
VTS_I4;
InvokeHelper (0x7, DISPATCH_METHOD, VT_EMPTY, NULL, parms, bQuad);
}
Затем подключите оба новых файла к проекту Project > Add Existing Item.
  
Управление с помощью объекта класса-оболочки
Для управления внедренным элементом ActiveX надо ввести в существующий диалоговый класс CTestGLDlg объект (переменную типа) класса-оболочки. Этот шаг тоже автоматизирован в Studio.Net, так как введение объекта влечет сразу несколько строк изменения кода.

  1. Поставьте фокус на окно внедренного элемента IDC_OPENGL в форме диалога и вызовите контекстное меню.
  2. В меню выберите команду Variable, которая запустит мастер Add Member Variable Wizard.
  3. Установите флажок Control Variable и задайте в полях диалоговой страницы мастера следующие значения: Access — public, Variable type — COpenGL, Variable name — * m_Ctrl, Control ID - IDC_OPENGL
  4. Обратите внимание на то, что в.поле Control type уже выбран тип элемента OCX, и нажмите кнопку Finish.

Результатом работы мастера являются следующие строки программы:

  • объявление переменной COpenGL m_ctrl; в файле заголовков TestGLDlg.h;
  • вызов функции DDX_Control(pDX, IDC_OPENGL, m_ctrl), связывающей элемент управления в окне диалога с переменной m_ctrl. Этот вызов вы найдете в теле функции CTestGLDlg::DoDataExchange;

Для обеспечения видимости вставьте в начало файла TestGLDlg.h директиву:
#include "opengl.h"
В конец файла Stdafx.h вставьте директивы подключения заголовков библиотеки OpenGL:
#include <gl/gl.h>
// Будем пользоваться OpenGL
#include <gl/glu.h>
Теперь следует поместить в окно диалога элементы управления. Здесь мы не будем пользоваться страницами свойств элемента, созданными нами в рамках предыдущего проекта. Вместо этого мы покажем, как можно управлять внедренным элементом ActiveX с помощью объекта m_ctrl. Перейдите в окно диалогового редактора и придайте окну диалога IDD_TESTGL_DIALOG.
Идентификаторы для элементов управления можно задать так, как показано в табл. 9.2.
Таблица 9.2. Идентификаторы элементов управления


Элемент

Идентификатор

Диалог

IDD_TESTGL_DIALOG

Кнопка Data File

IDCJILENAME

Кнопка Back Color

IDC.BKCLR

Переключатель Quads

IDC_QUADS

Переключатель Strips

IDC_STRIPS

Выпадающий список Fill Mode

IDC_FILL

Ползунок Light (X)

IDC_XPOS

Кнопка Close

IDOK

Для кнопки Quads установите свойство Group в положение True, а для кнопки Strips — в False. Обе они должны иметь свойство Auto в состоянии True. Важно еще то, что числовые значения их идентификаторов должны следовать по порядку. Для кнопки Data File установите свойство DefaultButton. Для выпадающего списка снимите свойство Sort (сделайте его False) и слегка растяните вниз его окно в открытом состоянии, для этого сначала нажмите кнопку раскрывания. Для ползунка вы можете установить свойство Point в положение Top/Left. Обратите внимание на тот факт, что в режиме дизайна вы можете открыть с помощью правой кнопки мыши диалог со страницами свойств для элемента IDC_OPENGL, одну из которых мы создавали в предыдущем проекте. Теперь с помощью Studio.Net введите в диалоговый класс обработчики следующих событий:

  • OnClickedFilename — нажата кнопка IDC_FILENAME,
  • OnCiickedBkcir — нажата кнопка IDC_BKCLR,
  • OnSelchangeFill — изменился выбор в списке IDC_FILL,
  • OnClickedQuads — нажата кнопка IDC_QUADS,
  • OnHScroll — изменилась позиция ползунка IDC_XPOS,
  • OnClickedStrips — нажата кнопка IDC_STRIPS.

Ниже мы приведем тела этих функций, а сейчас отметим, что все они пользуются услугами класса-оболочки для прямого вызова методов СОМ-сервера. Однако, как вы могли заключить из рассмотрения кодов класса COpenGL, на самом деле вызов будет происходить с помощью интерфейса IDispatch, а точнее его метода Invoke. Функция cwnd: : invokeHelper, вызов которой вы видите во всех методах COpenGL, преобразует параметры к типу VARIANTARG, а затем вызывает функцию Invoke. Если происходит отказ, то Invoke выбрасывает исключение.
В диалоговом классе мы попутно произвели упрощения, которые связаны с удалением ненужных функций OnPaint и OnQueryDragicon. Эти изменения обсуждались при разработке приложения Look. Во избежание недоразумений, которые могут возникнуть в связи с многочисленным ручным редактированием, приведем коды как декларации, так и реализации класса CTestGLDlg:
//=== Декларация диалогового класса (Файл TestGLDlg.h)
#include "opengl.h"
#pragma once
class CTestGLDlg : public CDialog

{
public:
CTestGLDlg(CWnd* p = NULL);
enum
{
IDD = IDD_TESTGL_DIALOG
};
//======= Объект класса-оболочки
COpenGL m_Ctrl;
//======= Запоминаем способ изображения
BOOL m_bQuads;
//======= Реакции на регуляторы в окне диалога
void OnSelchangeFill(void);
void OnClickedFilename(void);
afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
void OnCiickedBkcir(void);
void OnClickedQuads(void);
void OnClickedStrips(void);
protected:
virtual
void DoDataExchange(CDataExchange* pDX) ;
virtual BOOL OnlnitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM IParam);
DECLARE_MESSAGE_MAP()
};
В файл реализации методов класса мы кроме функций обработки сообщений от элементов управления вставили код начальной установки этих элементов. Для этой цели нам опять понадобилась связь с сервером, которую обеспечивает объект m_ctrl класса-оболочки. Характерным моментом является то, что обрабатываем событие WM_HSCROLL, которое поступает окну диалога, вместо того чтобы обработать уведомляющее событие NM_RELEASEDCAPTURE, которое идет от элемента типа Slider Control. Такая тактика позволяет реагировать на управление ползунком клавишами, а не только мышью:
#include "stdafx.h"
#include "TestGL.h"
#include "TestGLDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = _FILE_;
#endif
//====== Пустое тело конструктора
CTestGLDlg::CTestGLDlg(CWnd* p) : CDialog(CTestGLDlg::IDD, p){}
void CTestGLDlg::DoDataExchange(CDataExchange* pDX) {
//====== Связывание переменной с элементом
DDX_Control(pDX, IDCJDPENGL, m_Ctrl);
CDialog::DoDataExchange(pDX);
}
//====== Здесь мы убрали ON_WM_PAINT и т. д.
BEGIN_MESSAGE_MAP(CTestGLDlg, CDialog) ON_WM_SYSCOMMAND()
//
}
}
AFX_MSG_MAP
ON_CBN_SELCHANGE(IDC_FILL, OnSelchangeFill)
ON_BN_CLICKED(IDC_FILENAME, OnClickedFilename)
ON_WM_HSCROLL()
ON_BN_CLICKED(IDC_BKCLR, OnClickedBkclr)
ON_BN_CLICKED(IDC_QUADS, OnClickedQuads)
ON_BN_CLICKED(IDC_STRIPS, OnClickedStrips)
END_MESSAGE_MAP()
//===== CTestGLDlg message handlers
BOOL CTestGLDlg::OnInitDialog()
{
//====== Добываем адрес меню управления окном
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu)
{
//====== Добавляем команду About
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,
IDM_ABOUTBOX,"About...");
}
//====== Загружаем стандартный значок
HICON hMylcon = ::LoadIcon(0,(char*)IDI_WINLOGO);
Setlcon(hMylcon, TRUE); // Set big icon Setlcon(hMylcon, FALSE);
// Set small icon
CDialog::OnInitDialog();
//====== Начальная установка элементов
CComboBox *pBox = (CComboBox*)GetDlgltem(IDC_FILL);
pBox->AddString("Points"); pBox->AddString("Lines");
pBox->AddString("Fill"); pBox->SetCurSel (2);
//==== Выясняем состояние режима изображения полигонов
m_Ctrl.GetQuad(&m_bQuads);
WPARAM w = m_bQuads ? BST_CHECKED : BST_UNCHECKED;
//===== Устанавливаем состояние переключателя
GetDlgltem(IDC_QUADS)->SendMessage(BM_SETCHECK, w, 0);
w = m_bQuads ? BST_UNCHECKED : BST_CHECKED;
GetDlgltem(IDC_STRIPS)->SendMessage(BM_SETCHECK, w, 0);
return TRUE;
}
void CTestGLDlg::OnSysCommand(UINT nID, LPARAM iParam)
{
if ((nID S OxFFFO) == IDM_ABOUTBOX)
{
CDialog(IDD_ABOUTBOX).DoModal();
}
else
{
CDialog::OnSysCommand(nID, IParam);
}
}
//====== Выбор из списка типа Combo-box
void CTestGLDlg::OnSelchangeFill(void) "'*
{
DWORD sel = ((CComboBox*)GetDlgltem(IDC_FILL))->GetCurSel();
sel = sel==0 ? GL_POINT : sel==l ? GL_LINE
: GL_FILL;
m_Ctrl.SetFillMode(sel);
}
//==== Нажатие на кнопку запуска файлового диалога
void CTestGLDlg::OnClickedFilename(void)
{
m_Ctrl.ReadData();
}
//====== Реакция на сдвиг ползунка
void CTestGLDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
//====== Выясняем текущую позицию, которая не во
//====== всех случаях отражена в параметре nPos
nPos = ((CSliderCtrl*)GetDlgItem(IDC_XPOS))->GetPos() ;
m_Ctrl.SetLightParam (0, nPos);
}
//====== Запускаем стандартный диалог
void CTestGLDlg::OnClickedBkclr(void)
{
DWORD clr = m_Ctrl.GetFillColor() ;
CColorDialog dig (clr);
dig.m_cc.Flags |= CC_FULLOPEN;
if (dlg.DoModal()==IDOK)
{
m_Ctrl.SetFillColor(dlg.m_cc.rgbResult);
}
}
//====== Запоминаем текущее состояние и
//====== вызываем метод сервера
void CTestGLDlg::OnClickedQuads(void)
{
m_Ctrl.SetQuad(m_bQuads = TRUE);
}
void CTestGLDlg::OnClickedStrips(void)
{
m_Ctrl.SetQuad(m_bQuads = FALSE);
}
В настоящий момент вы можете запустить приложение, которое должно найти и запустить DLL-сервер ATLGL, генерирующий изображение по умолчанию и демонстрирующий его в окне внедренного элемента типа ActiveX. Сервер должен достаточно быстро реагировать на изменение регулировок органов управления клиентского приложения.
Подведем итог. В этом уроке мы научились:

  • вносить функциональность окна OpenGL в окно, управляемое ATL-классом CWindow;
  • добавлять с помощью Studio.Net новые методы в интерфейс, представляемый ко-классом;
  • учитывать особенности обработки сообщений Windows в рамках ATL;
  • управлять контекстом передачи OpenGL, связанным с окном внедренного СОМ-объекта;
  • создавать приложение-контейнер на базе MFC и пользоваться услугами класса-оболочки для управления СОМ-объектом.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика