Требования к аппаратному обеспечению
|
При тестировании Power++ корпорации Sybase мы столкнулись с некоторыми сложностями, заставившими нас значительно отклониться от “программы испытаний”. Дело в том, что Power++ не позволяет создавать ActiveX-компоненты (в смысле ActiveX-control, он же OCX) и не поддерживает, без посторонней помощи, технологий DCOM, что заставило нас вместо ActiveX-компонента создать Power++ компонент TreeView. От DCOM-теста пришлось просто отказаться.
Вообще,
Power++ производит впечатление несомненно
оригинального продукта – со всеми вытекающими
отсюда достоинствами и недостатками. Очень
забавно выглядит Drag/Drop компонента с формы на
CodeEditor с появлением при этом “Reference Card” Wisard’a для
встраивания в код вызова методов (своеобразная
замена отсутствующего Complete Word’a). Reference Card можно
вызвать и просто через меню Help.
Относительно неплохой Help позволяет достаточно легко найти содержащуюся в нем информацию – жаль, что она не всегда соответствует действительности. Например, при вызове метода “Expand(hTVItem);” для компонента TreeView срабатывает событие Expand (т.е. метод триггерит), хотя в Help сказано: “This method does not generate an Expand event” (Этод метод не генерирует событие Expand). Но такие казусы случаются и с продуктами других производителей, поэтому не будем слишком заострять внимание на такой мелочи. Повторимся, что Help, в целом, неплохой, обнаружить искомую информацию в нем можно без особого труда (при условии, что она там есть). Этим Sybase выгодно отличается от Inprise (Borland), в продуктах которого Help построен очень системно и, да простят нас за эти слова, “объектно-ориентированно”, но обнаружить там даже имеющуюся информацию не легко. В продуктах Microsoft Help сделан очень профессионально и с вселенским размахом (например, даже поиск слова mother выдал более 40 вхождений), так что найти в нем можно всё, что угодно. Другой вопрос, сколько сил и времени на это уйдёт... Так что Help от Sybase является золотой серединой — небольшой, но легко читаемый и “причесанный”. Отчасти простота поиска в Help’е может объясняться хорошей продуманностью библиотек. Продуманность и изящность - это главная отличительная особенность Power++.
Вообще, заканчивая разговор о Help’e, хотелось бы сослаться на очень показательный пример – да, при работе с Power++ нам действительно пришлось залезть пару раз в исходные тексты, но при использовании Delphi приходится это делать постоянно.
Если от библиотек Power++ мы были в восторге, то работа с API нас сильно утомила. По умолчанию Windows API не подключено – следуя инструкции по подключению (описана в Help на “API functions”) и, добавив после этого в cpp-файл #include “CommCtrl.h”, действительно удалось использовать API-функции для компонента TreeView, однако это не сработало при создании Power++-компонента CtestTreeView — компилятор обнаруживал какие-то несусветные синтаксические ошибки в исходном файле “…\Power21\w32sdk\h\nt\prsht.h”. В связи с этой неприятностью пришлось переопределять в коде все необходимые константы и структуры для вызова API-функции “TreeView_SetItem”, взяв их из исходного файла “CommCtrl.h”, – а другого метода принудительно установить плюс для ветки дерева, не имеющей дочерних ветвей, в Power++ мы не нашли.
Развернутые сообщения об ошибках, появляющиеся прямо в Code Editor’e – это интересная находка, хотя, наверное, лучше было бы выводить их где-нибудь в другом месте, или, хотя бы, дать возможность отключить эту опцию. Дело в том, что при наличии двух-трех таких сообщений код становится трудночитаемым и практически нередактируемым (поскольку сообщения об ошибках редактировать и удалять не разрешается) — сообщения удаляются из кода только после записи проекта. Параллельно сообщения об ошибках выводятся в отдельное окно Error Log.
Особо хочется отметить работу в режиме отладки компонента с запуском содержащего его тестового приложения. Дело в том, что мы не нашли возможности подключать библиотеку с нашим компонентом в run-time режиме (как это делается, например, в Delphi) — по этой причине после каждого изменения кода компонента приходится сначала запускать его проект на выполнение (при этом компонент инсталлируется в Power++), затем переходить в проект с тестовым приложением и, сделав какое-нибудь бессмысленное изменение (иначе Exe-файл не будет переделан), запускать его. Если же мы хотим поработать в режиме отладки компонента, то надо вновь возвращаться в его проект и запускаться оттуда — все это очень неудобно. Сбой точек прерывания при отладке компонента (произвольное перескакивание на другие, даже закоментаренные, строки и т.п.), представленный на рисунке 4, вероятнее всего возникает именно по причине пропуска какого-либо из вышеописанных действий (т.е. когда новый код компонента не соответствует его же старому коду, прилинкованному к Exe-файлу). Вообще, режим отладки явно недоработан.

Рисунок 4.
Мы столкнулись с проблемой поиска места, откуда можно инициализировать окно компонента TreeView. Мыпопытались сделать это в конструкторе, но нас ждало разочарования, так как окно на этот момент еще отсутствовало, а вызов конструктора происходил дважды для одного и того же компонента. Мы попытались найти решение в Help’e, но ответа не нашли (мы долго искали!). Поиск по исходникам натолкнул нас на мысль переопределить virtual-методы MakeWindow и LoadWindow (файл Wwindow.hpp), и на их вызов выполнять инициализацию переменных и создание динамических объектов (query_1 и пр.). Как мы впоследствии узнали из Help’a на “Known problems”, все WWindow::Create-методы вызывают MakeWindow или LoadWindow. Если форма, включающая в себя компонент, является диалогом, то срабатывает LoadWindow, иначе вызывается MakeWindow. В целом, наше решение сработало, за исключением того, что инициализация происходит каждый раз при изменении свойств данного окна. Решение этой проблемы попробуйте найти сами.
Cбои в среде разработки Power++ происходят по самым разным причинам (чаще всего, неочевидным). Например, удавалось вылететь при вызове списка Class в Object Inspector’e, и при выполнении некоторых действий в Watch List’e (по привычке после Delphi). Однако, ради справедливости отметим, что Delphi 3 и C++ Builder имеют такую же привычку, и при работе с ActiveX’ами иногда поражают воображение программиста как своими труднопредсказуемыми сбоями, так и удивительными сообщениями об ошибках. Delphi4, на наш взгляд, несколько более надежна.
При создания Power++ компонента мы старались, насколько это возможно, повторить то же самое, что делалось с аналогичными тестами на других средствах разработки, но полного подобия достичь, очевидно, невозможно из-за нежелания Power++ создавать ActiveX-компоненты.
Сделаем “Power++”-компонент, для чего сначала создадим библиотеку компонентов (рис. 6), после чего следуя инструкциям, изложенным в Help по адресу “Create a component“, выберем File/New/Class/Native Component (рис. 7) и доведем до конца появившийся Wizard. Запустим проект на выполнение (при этом библиотека будет установлена в Power++) и, для отладки компонента, создадим тестовое приложение. Оно подключается к проекту с отлаживаемым компонентом (рис. 8) через Run Options (описано в Help на “Debugging a design-time component library”), однако это имеет смысл только в том случае, когда мы подгружаем свой компонент динамически (описав это в коде тестового проекта). В принципе, в Help сказано, что все приложения по умолчанию используют “run-time”-версии библиотек, при этом их текст не включается в текст исполняемого файла (.exe), что сокращает время линковки и делает более удобной отладку (“Programs prepared with the run-time version of the library have smaller executable files and are faster to link… …By default, all executable files use the run-time version of the Power++ library”). Этот же параметр можно установить в настройках для проекта: если открыть View/Targets и, выбрать Default Options/Exe(Test1), вызвать Properties во всплывающем по правой кнопке мыши меню, то на закладке Executable Size должен быть выделен режим “Use the run-time DLL”. Все это хорошо, и даже прекрасно – но по какой-то причине не работает с компонентами, созданными в Power++, поэтому приходится при внесении изменений в компонент пересоздать полностью сначала его, потом тестовое приложение, а затем снова возвращаться в проект с компонентом для его отладки (из-за такого неудобства мы всего-лишь пару раз пользовались точкоми прерывания при отладке компонента, когда просто нельзя было обойтись без просмотра содержимого переменных, а в основном запускали тестовое приложение из его же проекта).

Рисунок 5. Сообщения об ошибках, появляющиеся в Code Editor.
Для добавления в компонент функций (user functions), свойств, методов и событий при создании компонентов используются Wizard’ы из меню, всплывающего при щелчке правой кнопкой мыши на имени компонента в окне Classes (Insert/Property).

Рисунок 6.

Рисунок 7.
Рисунок 8.
Добавим свойство CurID_RT с типом WLong (во избежание лишних проблем не меняя другие значения по умолчанию, кроме типа данных). На этом работа с автоматизацией в нашем проекте закончилась. Остальной код был введен вручную.
Единственным серьёзным отличием от реализации в других средах разработки стала ликвидация метода Сonnect - по лени.
Рисунок 9. Property Wizard
Мы не стали подробно комментировать код, поскольку, в принципе, все точно так же, как и при работе с уже описанными приложениями. Выполнив двойной щелчок левой кнопки мыши на названии компонента откроем CodeEditor и внесем в него:
public:
// add your public instance data here
WBool MakeWindow( WWindow * parent,
WUInt id, const WChar *className, const WChar *title,
const WRect & r, WStyle wstyle, WStyle exStyle,
void *data=NULL);
WBool LoadWindow( WWindow * parent, const WResourceID & id,
WModuleHandle
module=_ApplicationModule );
private:
// add your private instance data here
protected:
// add your protected instance data here
void onExpand( WObject * source, WTreeViewEventData * event );
void onEndLabelEdit( WObject * source,
WTreeViewDispEventData * event );
void onCollapse( WObject * source, WTreeViewEventData * event );
void miAdd_onClick(WObject * source, WEventData * event);
void miDelete_onClick(WObject * source, WEventData * event);
void InitControls();
void __fastcall set_CurID_RT(long Value);
long __fastcall get_CurID_RT();
WTreeViewItemHandle __fastcall ExpandParentNode(long iID_RT);
WTreeViewItemHandle __fastcall FindChildByData(
WTreeViewItemHandle TVItemParent,
void * pData);
WBool __fastcall TreeViewCallBack1 (WTreeView * treeView,
WTreeViewItemHandle item,
void * userData );
//Описание переменных
WTreeViewItemHandle tviRoot; //Указатель на корневой (первый) элемент дерева
WTransaction * tran;
WPopupMenu * ttt;
WMenuItem * miDelete, * miAdd;//Указатели на элементы всплывающего меню
WQuery * query_1;
WImageList * ImageList1;
public:
// CurID_RT Property
WLong GetCurID_RT() const;
WBool SetCurID_RT( WLong newValue );
public:
CtestTreeView();
public:
~CtestTreeView();
};
// Code added here will be included at the top of the .CPP file
// Include definitions for resources.
#include “WRes.h”
//описание взято из исходника “CommCtrl.h” (подключить этот header-файл в данный
//компонент нам не удалось)
#define TVIF_CHILDREN 0x0040
#define TV_FIRST 0x1100 // TreeView messages
#define TVM_SETITEMA (TV_FIRST + 13)
#define TVM_SETITEMW (TV_FIRST + 63)
#ifdef UNICODE
#define TVM_SETITEM TVM_SETITEMW
#else
#define TVM_SETITEM TVM_SETITEMA
#endif
typedef WLong LPARAM;
typedef WUInt UINT;
typedef HANDLE HTREEITEM;
typedef struct _TV_ITEM
{
UINT mask;
WTreeViewItemHandle hItem;
UINT state;
UINT stateMask;
LPSTR pszText;
int cchTextMax;
int iImage;
int iSelectedImage;
int cChildren;
LPARAM lParam;
} TV_ITEM, FAR *LPTV_ITEM;
//Конструктор
CtestTreeView::CtestTreeView()
{
}
//Деструктор
CtestTreeView::~CtestTreeView()
{
}
//На MakeWindow или LoadWindow выполняется инициализация (InitControls())
WBool CtestTreeView::MakeWindow( WWindow * parent, WUInt id,
const WChar *className,
const WChar *title,
const WRect & r,
WStyle wstyle, WStyle exStyle,
void * data )
{
WBool b = WTreeView::MakeWindow(parent, id, className, title, r,
wstyle, exStyle, data);
InitControls();
return b;
}
WBool CtestTreeView::LoadWindow( WWindow * parent,
const WResourceID & id,
WModuleHandle module)
{
WBool b = WTreeView::LoadWindow(parent, id, module);
InitControls();
return b;
}
void CtestTreeView::InitControls()
{
//Создание компонента tran (класс WTransaction) и связь его с базой данных
...//через
//существующее ODBC-соединение. Очень важно не забыть tran->Connect, т.к. по
//умолчанию компонент WTransaction в Desighn-time создается со свойством
//AutoConnect = True, но этого свойства в run-time нет.
tran = new WTransaction();
tran->SetDBMSName(“ODBC”);
tran->SetDataSource(“Test”);
tran->SetUserid(“sa”);
tran->SetCursorDriver(WTCDNative);
tran->Connect();
//Создание компоеннта query_1 (класс WQuery) и подключение к нему
//компонента tran
query_1 = new WQuery();
query_1->SetTransactionObject(tran);
//Создание всплывающего меню ttt (класс WPopupMenu), создание и подключение
//элементов меню, подключение меню к компоненту.
ttt = new WPopupMenu(“ttt”, true);
miAdd = new WMenuItem(0, “Add”, “”, WTextMenuItem);
miDelete = new WMenuItem(0,”Delete”, “”, WTextMenuItem);
ttt->AddItem(miAdd, 1, true);
ttt->AddItem(miDelete, 1, true);
SetPopup(ttt);
//Создание первой (корневой) ветки
tviRoot = Add( “Root”, NULLHTVITEM, 0, -1, 0, NULLHTVITEM,
(void *) 0,
WTVC_HAS_CHILDREN );
//Установка обработчиков для событий компонента
SetEventHandler( WExpandEvent, this,
WEventHandlerCast(CtestTreeView, onExpand));
SetEventHandler( WCollapseEvent, this,
WEventHandlerCast(CtestTreeView, onCollapse));
SetEventHandler( WEndLabelEditEvent, this,
WEventHandlerCast(CtestTreeView,
onEndLabelEdit));
miAdd->SetEventHandler( WClickEvent, this,
WEventHandlerCast(CtestTreeView,
miAdd_onClick));
miDelete->SetEventHandler( WClickEvent, this,
WEventHandlerCast(CtestTreeView,
miDelete_onClick));
}
void CtestTreeView::onExpand( WObject * source,
WTreeViewEventData * event )
{
WDataValue value;
WString sSQL;
//Получение ID_RT для выделенной ветки и формирование SQL-запроса для получения
//названий, ID_RT дочерних веток и количества дочерних веток у них
WLong lUserData = (WLong)(GetUserData( event->item ));
query_1->Close();
sSQL.Sprintf( “select ID_RT, Name, \r\n”
“ (select count(*) from RT as RT1 \r\n”
“ where RT1.Parent = RT.ID_RT \r\n”
“ and RT1.ID_RT <> RT.ID_RT) as HasChildren \r\n”
“ from RT \r\n”
“ where Parent = %d and ID_RT <> Parent \r\n”
“ order by Name”, lUserData);
query_1->SetSQL( sSQL );
if (!query_1->Open())
{
WMessageBox mb;
mb.SetText(“Произошла ошибка при попытке чтения из базы данных!”);
mb.Prompt();
return;
};
//Создание дочерних веток для раскрывающейся (выделенной) ветки в компоненте
while (query_1->FetchNext( FALSE, FALSE ))
{
WLong lID_RT = query_1->GetValue( 1 ).GetSLONG( );
WString sName = query_1->GetValue( 2 ).GetCHAR( );
WTreeViewCChildren HasChildren =
query_1->GetValue( 3 ).GetSLONG( ) != 0
? WTVC_HAS_CHILDREN : WTVC_FIELD_UNUSED;
Add( sName, event->item, 0, -1, 0, NULLHTVITEM, (void *)lID_RT,
HasChildren );
}
return;
}
void CtestTreeView::onCollapse( WObject * source,
WTreeViewEventData * event )
{
//На закрытие ветки удалить все дочерние ветки
//(Метод Collaps не вызывает события onCollapse)
Collapse(event->item, TRUE);
return;
}
void CtestTreeView::onEndLabelEdit(WObject * source,
WTreeViewDispEventData * event )
{
WQuery sqlUpdateNodeName;
WString sSQL;
//Создается новый объект WQuery, выполняется изменение имени для
//редактируемой ветки
sqlUpdateNodeName.SetTransactionObject(
query_1->GetTransactionObject());
sSQL.Sprintf(“UPDATE RT SET Name = ‘%s’ WHERE ID_RT = %d “,
event->itemText, long(GetUserData(event->itemHandle)));
if (!sqlUpdateNodeName.Execute(sSQL))
{
WMessageBox mb;
mb.SetText(“Произошла ошибка при попытке записи в базу данных!”);
mb.Prompt();
return;
};
//Обновление данных в query_1
query_1->Close();
query_1->Open();
return;
}
WBool CtestTreeView::SetCurID_RT(WLong newValue)
{
WTreeViewItemHandle hTVItem = (WTreeViewItemHandle)NULL;
WLong iOldValue;
if (!query_1->GetOpened()) return false;
if (newValue < 0) return false;
hTVItem = GetSelectedItem();
iOldValue = ( hTVItem == NULL) ? -1
: (WLong)(GetUserData(hTVItem));
if (newValue == iOldValue) return false;
//Раскрыть все ветки, которые являются предками искомой
hTVItem = ExpandParentNode(newValue);
//Перейти на ветку с переданным значением newValue = ID_RT
// hTVItem является Parent’ом для искомой ветки
FindChildByData(hTVItem, (void *)newValue);
return false;
};
WLong CtestTreeView::GetCurID_RT() const
{
//Получить ID_RT выделенной ветки, если такая есть, иначе вернуть -1
WLong iID_RT = (GetSelectedItem() != NULL) ?
(WLong) (GetUserData(GetSelectedItem())) : -1;
return iID_RT;
}
WTreeViewItemHandle __fastcall CtestTreeView::ExpandParentNode(
long iID_RT)
{
WTreeViewItemHandle TempResult = (WTreeViewItemHandle)NULL;
WQuery sqlFindID_RT;
WLong iParentID;
WString sID_RT;
//Создать новый объект WQuery и выполнить запрос
//для нахождения ветки-предка для данной ветки
sqlFindID_RT.SetTransactionObject(
query_1->GetTransactionObject());
sID_RT.Sprintf(“%d”,iID_RT);
sqlFindID_RT.SetSQL(“SELECT Parent FROM RT”
” WHERE ID_RT = “ + sID_RT);
sqlFindID_RT.Open();
sqlFindID_RT.FetchNext( FALSE, FALSE );
iParentID = sqlFindID_RT.GetRowCount(false) == 0 ? –1
:sqlFindID_RT.GetValue(1).GetSLONG();
if (iParentID == -1) return TempResult;
if (iParentID == 0)
{
//Если найден корень (Root), то раскрыть его и выйти в из функции
TempResult = tviRoot;
Expand(TempResult);
return TempResult;
}
//Рекурсивный вызов ExpandParentNode
TempResult = ExpandParentNode(iParentID);
//На выходе из рекурсии каждый находить ветку, для которой ранее
//искался Parent, и раскрывать ее
if (TempResult == NULL) return TempResult;
TempResult = FindChildByData(TempResult, (void *)iParentID);
if (TempResult != NULL) Expand(TempResult);
return TempResult;
}
WTreeViewItemHandle __fastcall CtestTreeView::FindChildByData(WTreeViewItemHandle TVItemParent,
void * pData)
{
WTreeViewItemHandle hTChild;
//Перебрать все дочерние ветки для TVItemParent и найти ту,
//ID_RT которой совпадает с искомым (ID_RT хранится в Data в виде (void *)
EnumerateChildren(TVItemParent, this,
(WTreeViewCallback)&TreeViewCallBack1, pData);
hTChild = GetSelectedItem();
return hTChild;
}
WBool __fastcall CtestTreeView::TreeViewCallBack1 (
WTreeView * treeView, WTreeViewItemHandle item,
void * userData )
{
//Эта Call-Back-функция вызывается из EnumerateChildren.
// Она повторно выполняется до тех
//пор, пока не возвращает false или не заканчивается список дочерних веток
if (GetUserData(item) == userData )
{
SetSelectedItem(item);
return false;
}
return true;
}
void CtestTreeView::miAdd_onClick(
WObject * source,
WEventData * event )
{
WString sSQL;
WString sName = “Новый элемент №”;
WTreeViewItemHandle hTVItem = GetSelectedItem();
WLong lUserData = (WLong)(GetUserData(hTVItem));
WQuery query_temp;
query_temp.SetTransactionObject(query_1->GetTransactionObject());
//Отключение AutoCommit позволяет держать транзакцию сколько нужно,
//что позволяет выполнить несколько запросов и не беспокоиться, что
//другой пользователь в это же время изменяет данные
tran->SetAutoCommit(false);
//Получение номера следующей записи с помощью специальной таблицы Counters
//которая содержит единственное поле,увеличивающееся на 1 при каждой новой
//записи в таблицу RT. В принципе, лучше было бы оформить этот запрос как
//Stored-procedur’у с именем, например,“Next”, и вызывать ее при каждой
//вставке записи.
query_temp.SetSQL(“update Counters set CounterValue =
CounterValue + 1”);
if (!query_temp.Execute())
{
WMessageBox mb;
mb.SetText(“Произошла ошибка при попытке записи в базу данных!”);
mb.Prompt();
return;
};
//Получаем новое ID_RT из таблицы Counters
query_temp.SetSQL(“select CounterValue \r\n”
“from Counters where ID_Counter = 1”);
if (!query_temp.Open())
{
WMessageBox mb;
mb.SetText(“Произошла ошибка при попытке записи в базу данных!”);
mb.Prompt();
return;
};
query_temp.FetchNext( FALSE, FALSE );
WLong lNew = query_temp.GetValue( 1 ).GetSLONG( );
query_temp.Close();
//Сформировать название для новой записи
sName.Sprintf(“%s %d”, sName.GetText(), lNew);
//Вставить новую запись в таблицу RT
sSQL.Sprintf(“insert into RT (ID_RT, Parent, Name, ImageNum) \r\n”
“values (%d, %d, ‘%s’, 0)”
,lNew, lUserData, sName.GetText());
query_temp.SetSQL(sSQL);
if (!query_temp.Execute())
{
WMessageBox mb;
mb.SetText(“Произошла ошибка при попытке записи в базу данных!”);
mb.Prompt();
return;
};
//Выполнить Commit вручную и разрешить доступ к базе данных
//другим пользователям
tran->Commit();
tran->SetAutoCommit( true );
//Если ветка-предок, в которую добавляется дочерняя ветка, открыта, то просто
//добавить новую ветку, иначе с помощью Windows API добавить плюс к
//ветке-предку (т.к. у нее могло не быть дочерних веток на этот момент,
//а Power++ не дает возможности прибавить плюс к ранее созданной ветке не
//добавляя дочерних веток) и раскрыть ее и выделить новую ветку
if (GetExpanded(hTVItem))
{
Add( sName.GetText(), hTVItem, 0, -1, 0, NULLHTVITEM,
(void *) lNew);
}
else
{
TV_ITEM tvi;
tvi.mask = TVIF_CHILDREN;
tvi.hItem = hTVItem;
tvi.cChildren = 1;
SendMessage(WMessage( TVM_SETITEM, WUInt(0), WLong(&tvi)));
Expand(hTVItem);
FindChildByData(hTVItem, (void *)lNew);
};
//обновить query_1
query_1->Close();
query_1->Open();
return;
}
void CtestTreeView::miDelete_onClick(
WObject * source,
WEventData * event )
{
WQuery sqlDelete;
WString sSQL;
WTreeViewItemHandle hTVItem = GetSelectedItem();
sqlDelete.SetTransactionObject(query_1->GetTransactionObject());
sSQL.Sprintf( “delete from RT where ID_RT = %d”,
(WLong)GetUserData(hTVItem));
sqlDelete.SetSQL( sSQL );
if (!sqlDelete.Execute())
{
WMessageBox mb;
mb.SetText(“Произошла ошибка при попытке удаления записи из ”
“базы данных!”);
mb.Prompt();
return;
};
Delete(hTVItem);
query_1->Close();
query_1->Open();
return;
}
В код тестового проекта надо добавить обработчики событий Click для кнопок GetCurID_RT и SetCurID_RT, подобно тому, как мы это делали в предыдущих примерах:
WBool Form1::SetCur_ID_Click(
WObject * source,
WEventData * event )
{
ctesttreeview_1->SetCurID_RT(atoi(CurID_RT->GetText()));
return FALSE;
}
WBool Form1::GetCur_ID_Click(
WObject * source,
WEventData * event )
{
WString sTemp;
sTemp.Sprintf("%d", ctesttreeview_1->GetCurID_RT());
CurID_RT->SetText(sTemp);
return FALSE;
}
В начале нашей речи о Power++ мы немного погрешили против правды — все-таки некоторая поддержка ActiveX в нем имеется, а именно, есть возможность создания AсtiveX-server’a. На самом деле это минимальный COM-объект, реализованный в виде DLL. Это означает, что его можно запустить только в локальном процессе или под управлением MTS. Поддержка распределенной технологии в Power++ вообще реализована пока только для Jaguar и MS Transaction Server с помощью компонента ActiveX-server. Возможно, это — политика фирмы.
Работа с базой данных в Power++ реально возможна только через ODBC, что влечет за собой ряд ограничений на сложность SQL-запросов. Наш пример использует существующее ODBC-соединение “Test”, настроенное на работу с MS-SQL Server.
В целом, Sybase Power++ произвел на нас впечатление достаточно “сырого” продукта, который, однако, заметно совершенствуется от версии к версии (разница между 1.0 и 2.1 более чем ощутима). В рассматриваемом нами аспекте (COM-DCOM) Power++ отстает и пока не может конкурировать с Delphi, C++ Builder или Visual C++, но, возможно, впоследствии это отставание будет если не ликвидировано, то по крайней мере сокращено. Несмотря на все недоработки, в Power++ значительно реже приходится прибегать к “магическим” действиям типа “TreeView.Selected := TreeView.Selected”, фокус в импортированных ActiveX’ах работает нормально, даже если эти ActiveX’ы были созданы в Delphi, чего нельзя сказать о намного более продвинутых и бесконечно более загадочных продуктах Inprise. Для работы с базами данных у компании Sybase предусмотренно значительно более удобное, чем у любого из конкурирующих продуктов, средство DataWindow, реализованное в Power++ в виде ActiveX-компонента. С его помощью визуально создаются формы, отчеты или просто таблицы. Оно, разумеется, не всемогуще, но при корпоративном использовании позволяет быстро добиться вполне приемлемых результатов.
Так как работа с API и расширяемость Power++ оставляют желать лучшего, то для системной разработки лучше использовать другие продукты. Однако при внутрикорпоративном использовании хорошо продуманные библиотеки, удобные компоненты типа DataWindow, решающие большинство проблем доступа к корпоративным БД и легкий в использовании Help делают Power++ весьма неплохим выбором — особенно, если вы уже используете Power Builder той же фирмы.