Продолжается подписка на наши издания! Вы не забыли подписаться?

Sybase Power ++ 2.1

Требования к аппаратному обеспечению

16MB оперативной памяти (или больше)
Процессор 66MHz 486 или 66MHz PowerPC
(или быстрее)
видеокарта, поддерживающая 256 цветов
с разрешением 640 x 480
Microsoft Internet Explorer (версии 3.0 для
Windows 95, 2.1 для Windows 3.1 и Macintosh)
или Netscape Navigator 3.0

При тестировании Power++ корпорации Sybase мы столкнулись с некоторыми сложностями, заставившими нас значительно отклониться от “программы испытаний”. Дело в том, что Power++ не позволяет создавать ActiveX-компоненты (в смысле ActiveX-control, он же OCX) и не поддерживает, без посторонней помощи, технологий DCOM, что заставило нас вместо ActiveX-компонента создать Power++ компонент TreeView. От DCOM-теста пришлось просто отказаться.

logo_cPower.bmp (444230 bytes)Вообще, 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, на наш взгляд, несколько более надежна.

Компонент TreeView, работающий с базой данных

При создания 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++, поэтому приходится при внесении изменений в компонент пересоздать полностью сначала его, потом тестовое приложение, а затем снова возвращаться в проект с компонентом для его отладки (из-за такого неудобства мы всего-лишь пару раз пользовались точкоми прерывания при отладке компонента, когда просто нельзя было обойтись без просмотра содержимого переменных, а в основном запускали тестовое приложение из его же проекта).

 err_Sample.bmp (132250 bytes)

Рисунок 5. Сообщения об ошибках, появляющиеся в Code Editor.

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

library_create.bmp (72470 bytes)

Рисунок 6.

create_component.bmp (428998 bytes)

Рисунок 7.

ppp8.GIF (14073 bytes)

Рисунок 8.

Добавим свойство CurID_RT с типом WLong (во избежание лишних проблем не меняя другие значения по умолчанию, кроме типа данных). На этом работа с автоматизацией в нашем проекте закончилась. Остальной код был введен вручную.

Единственным серьёзным отличием от реализации в других средах разработки стала ликвидация метода Сonnect - по лени.

ppp9.GIF (18730 bytes)

Рисунок 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 той же фирмы.


Copyright © 1994-2016 ООО "К-Пресс"