Двойственные интерфейсы Технология
SomeMethod;
(Поток сознания в скобках, по Джойсу или Жванецкому: новые концепции, новые технологии, глубина мыслей, отточенность деталей, настоящая теория должна быть красивой, тупиковая ветвь?, монополисты не только заставляют покупать, но и навязывают свой способ мышления, что бы ты делал без MS, о чем думал, посмотри CLSID в реестре, видел ли я полезный элемент ActiveX, нужно ли бесшовно внедрять что-нибудь во что-нибудь, посмотри Interfaces в реестре, что лучше, Stingray-класс или внедренная по стандарту OLE таблица Excel, тонкий (thin) клиент не будет иметь кода, но будет иметь много картинок и часто покупать дешевые сеансы обслуживания, как раньше билеты в кино или баню, если не поддерживать обратную совместимость, то кто будет покупать, лучше не купить, чем перестать играть в DOS-игры, стройный (slim) клиент, хочешь, еще посчитаю — плати доллар, перестань думать, пора работать.)
Дуальные или интерфейсы диспетчеризации (dispinterfaces) в отличие от тех vtable-интерфейсов, с которыми вы уже знакомы, были разработаны для того, чтобы реализовать позднее связывание (late-binding) клиента с сервером. Инструментальная среда разработки Visual Basic в этом смысле является лидером, так как в ней вы почти без усилий можете создать приложение, способное на этапе выполнения, то есть поздно, получить информацию от объекта и пользоваться методами интерфейсов, информация о которых стала доступной благодаря IDispatch.
Стандартные свойства
Возвращаясь к нашему проекту, отметим, что интерфейс юрепсъ предоставляет своим пользователям два одноименных метода FillColor. Первый метод позволяет пользователю изменить (propput) стандартное или встроенное (stock property) свойство: «цвет заливки». Второй — узнать (propget) текущее значение этого свойства. Этот интерфейс был вставлен мастером потому, что при создании элемента мы указали на -необходимость введения в него одного из стандартных свойств. С этой же целью мастер ввел в состав класса переменную:
OLE_COLOR m_clrFillColor;
которая будет хранить значение свойства. Мы должны ею управлять, поэтому давайте зададим начальное значение цвета в конструкторе класса. Найдите его и измените:
COpenGL()
{
m_clrFillColor = RGB (255,230,255);
}
Но этого мало. Для того чтобы увидеть результат, надо изменить коды функции рисования, которую вы найдете в том же файле OpenGLh.
Вступив в царство ATL, придется отречься от многих привычек, приобретенных в MFC. Вы уже заметили, что мы теперь вместо char* или CString пользуемся OLESTR, а вместо COLORREF— OLE_COLOR. Это еще не так отвлекает, но вот теперь надо рисовать без помощи привычного класса CDC и вернуться к описателю НОС контекста устройства, которым мы пользовались при разработке традиционного Windows-приложения на основе функций API. Также придется привыкнуть к тому, что описатель HOC hdcDraw упрятан в структуру типа ATL_DRAWINFO, ссылку на которую мы получаем в параметре метода OnDraw класса CComControl.
Напомню, что вся функциональность класса CComControl унаследована нашим классом COpenGL, который, кроме него, имеет еще 17 родителей. Состав полей структуры ATL_DRAWINFO не будем приводить здесь, чтобы не усугублять головокружение, а вместо этого предложим убедиться в том, что можно влиять на облик СОМ-объекта. Особенностью перерисовки СОМ-объекта является то, что он изображает себя в чужом окне. Поэтому, получив контекст устройства, связанный с этим окном, он должен постараться не рисовать вне пределов прямоугольника, отведенного для него. В Windows существует понятие поврежденной области окна (clip region). Это обычно прямоугольная область, в пределах которой система позволяет приложению рисовать. Если рисующие функции GDI попробуют выйти за границы этой области, то система не отобразит этих изменений. Следующий код интенсивно работает с clip region, поэтому для понимания алгоритма рекомендуем получить справку о функциях GetClipRgn и SelectClipRgn. Введите изменения в уже существующее тело функции OnDraw так, чтобы она приобрела вид:
HRESULT OnDraw(ATL_DRAWINFO& di)
{
//===== Преобразование RECTL в RECT
RECT& r = *(RECT*)di.prcBounds;
//===== Запоминаем текущую поврежденную область
HRGN hRgnOld = 0;
//== Функция GetClipRgn может возвратить: 0, 1 или -1
if (GetClipRgn(di.hdcDraw, hRgnOld) != 1) hRgnOld = 0;
//====== Создание новой области
HRGN hRgnNew = CreateRectRgn(r.left,r.top, r.right,r.bottom);
// Оптимистический прогноз (новая область воспринята)
bool bSelectOldRgn = false;
//=== Устанавливаем поврежденную область равной г
if (hRgnNew)
{
bSelectOldRgn = SelectClipRgn(di.hdcDraw,hRgnNew) == ERROR;
}
//=== Изменяем цвет фона и обрамляем объект
::rSelectObject(di.hdcDraw,
::CreateSolidBrush(m_clrFillColor)); Rectangle(di.hdcDraw, r.left, r.top,r.right,r.bottom);
//=== Параметры выравнивания текста и сам текст
SetTextAlign(di.hdcDraw, TA_CENTER | TA_BASELINE);
LPCTSTR pszText = _T("ATL 4.0 : OpenGL");
//=== Вывод текста в центр прямоугольника
TextOut(di.hdcDraw, (r.left + r.right)/2,
(r.top + r.bottom)/2,
pszText,Istrlen(pszText));
//=== Если был сбой, то устанавливаем старую область
if (bSelectOldRgn)
SelectClipRgn(di.hdcDraw, hRgnOld);
return S_OK;
}
В этой реализации функции OnDraw мы намеренно пошли на поводу у схемы, предложенной в заготовке. Структура RECTL, на которую указывает prcBounds, идентична структуре RECT, но при заливке она ведет себя на один пиксел лучше (см. справку). Здесь это никак не используется. Автору фрагмента не хотелось много раз писать выражение di. prcBounds->, поэтому он завел ссылку на объект типа RECTL, приведя ее к типу RECT. Здесь хочется «взять в руки» CRect, cstring и переписать фрагмент заново в более компактной форме, однако если вы попробуете это сделать, то получите сообщения о том, что CRect и cstring — неизвестные сущности. Они из другого царства MFC. Мы можем подключить поддержку MFC, но при этом многое потеряем. Одной из причин создания ATL была неповоротливость объектов на основе MFC в условиях web-страниц. Мы не можем себе этого позволить, так как собираемся работать с трехмерной графикой. Поэтому надо привыкать работать по правилам Win32-API и классов СОМ.
Тестирование объекта
Вновь запустите приложение и убедитесь в том, что нам удалось слегка подкрасить объект. Теперь исследуем функциональность, которую получили бесплатно при оформлении заказа у мастера.
Попробуем это исправить. Событие, заключающееся в том, что пользователь объекта изменил одно из его стандартных свойств, поддерживаемых страницами не менее стандартного диалога, будет обработано каркасом СОМ-сервера и при этом вызвана функция copenGL: :OnFillColorChanged, код которой мы не трогали. Сейчас там есть только одна строка:
ATLTRACE(_T ("OnFillColorChanged\n"));
которая в режиме отладки (F5) выводит в окно Debug Studio.Net текстовое сообщение. Внесите в тело этой функции изменения:
void OnFillColorChangedO
{
//====== Если выбран системный цвет,
if (m_clrFillColor & 0x80000000)
//====== то выбираем его по индексу
m_clrFillColor=::GetSysColor(m_clrFillColor & Oxlf); ATLTRACE(_T("OnFillColorChanged\n"));
}
Признаком выбора системного цвета является единица в старшем разряде m_clrFillColor. В этом случае цвет задан не тремя байтами (red, green, blue), a индексом в таблице системных цветов (см. справку по GetSysColor). Выделяя этот случай, мы выбираем системный цвет с помощью API-функции GetSysColor. Заодно подправим функцию перерисовки, чтобы убедиться, что объект нам подчиняется и мы умеем убирать лишний код:
HRESULT OnDraw(ATL_DRAWINFO& di)
{
//====== Не будем преобразовывать в RECT
LPCRECTL р = di.prcBounds;
//====== Цвет подложки текста
::SetBkColor(di.hdcDraw,m_clrFillColor) ;
//====== Инвертируем цвет текста
::SetTextColor(di.hdcDraw, ~m_clrFillColor & Oxffffff);
//====== Цвет фона
::SelectObject(di.hdcDraw,
::CreateSolidBrush(m_clrFillColor));
Rectangle(di.hdcDraw, p->left, p->top, p->right, p->bottom);
SetTextAlign(di.hdcDraw, TA_CENTER | TA_BASELINE);
LPCTSTR pszText = _T("ATL 4.0 : OpenGL");
TextOut(di.hdcDraw, (p->left + p->right)/2,
(p->top + p->bottom)/2,
pszText, Istrlen(pszText)
};
return S_OK;
}
Запустите и убедитесь, что системные цвета выбираются корректно, а перерисовка при изменении размеров объекта не нарушает заданных границ. Некоторые проблемы возникают при инвертировании цвета фона, если он близок к нейтральному (128, 128, 128). В качестве упражнения решите эту проблему самостоятельно.