Как всегда, буду говорить только то, что знаю, и делать так, как умею! Можете лучше, точнее – всегда «пожалуйста», жду Ваших критически горячих и обоснованных комментариев. Приступим. :))
Как инструмент, bBrowser возник достаточно давно, постепенно вытесняя конкурентов и самоделки. Но, я знаю несколько людей, которые с удовольствием продолжают использовать и развивать собственные разработки, ничуть не уступающие лидеру. Но, это я отклонился от темы. Возвращаюсь :)). Поэтому bBrowser «болеет», как болезнями «роста», так и – «хроническими» заболеваниями. Тут нужны уточнения.
Можно выделить несколько групп ошибок:
- Грубые ошибки разработчиков: заявляется одно – а делается (или не делается) другое.
- Мягкие ошибки, возникающие из-за того, что разработчики упрощают задачи, предполагая, что пользователи будут работать строго по правилам.
- Непредвиденные ошибки, как правило, вносимые самой средой или инструментом разработки (для bBrowser – это ошибки из CA-Visual Objects).
Болезни «роста» (я ими тоже подвержен) обычно возникают при скоростной разработке проекта, когда внимание переносится то на одну группу задач, то на другую. Внимание рассеивается, и отдельные вещи могут упускаться из виду. Тут надёжный помощник – план разработки (проект) и чёткое ему следование. Невзирая на давление со стороны заказчика. Т.к. сам заказчик зачастую не может компетентно решать, что сейчас важно, а что – нет. Известная тема, что не дело программиста «учить жизни» заказчика, и – наоборот.
Немцы достаточно скрупулезные товарищи, поэтому грубых ошибок почти нет. У них всегда есть план и они «морально устойчивы» в плане отклонения от схем. Основная масса ошибок состоит в том, что они не всегда внимательно отслеживают (или учитывают) поведение пользователей. Т.е., в большинстве случаев – обычное упрощение, из-за которого страдает надёжность библиотеки.
Из всего комплекта библиотеки я использую в полной мере всего три элемента:
- bBrowser Class
- bArrayServer Class
- bIcon Class
bIcon Class использую совсем немного, ничего существенного по нему не скажу. Хотя замечания есть. Поэтому основные замечания будут по классам «bBrowser» и «bArrayServer».
Проблемы bBrowser-а:
- Поддержка (отключение) тем Windows.
- Указание размеров габаритных элементов управления (контролов), типа ComboBox, при редактировании в ячейке.
- Отсутствие обработки события выбора «мышкой» в элементе ComboBox (и ему подобных) при редактировании в ячейке.
- Раскраска.
- Неполная поддержка AdoRecordSet.
Проблемы bArrayServer-а:
- Ошибки при создании индекса (ордера).
- Ошибки при удалении (очистке) ордера.
- Ошибки при копировании в файл.
- Недоработка в методе FieldPut.
- Ошибки получения информации об ордере.
Это не всё. Но, для начала хватит. :))
bBrowser.
1. Поддержка (отключение) тем Windows.
Смысл своего решения я свёл к использованию «продвинутой» функции:
FUNCTION ms_EnableAppVisualTheme( lEnable := TRUE AS LOGIC,;
liThemeFlags := -1 AS LONG ) AS LOGIC PASCAL
// Pencil 09.11.2011
// liThemeFlags:
// STAP_ALLOW_NONCLIENT - Specifies that the nonclient areas of
// application windows will have visual styles applied.
// STAP_ALLOW_CONTROLS - Specifies that the common controls
// used in an application will have visual styles applied.
// STAP_ALLOW_WEBCONTENT - Specifies that web content displayed
// in an application will have visual styles applied.
LOCAL dwStyle AS DWORD
// Сначала отрабатываем через bBrowser
bvsEnableVisualStyles( lEnable )
// Уже не надо, т.к. сделали это через bvsEnableVisualStyles
// EnableAppVisualTheme( lEnable )
// Принудительно делаем как в КаВо, т.к. у bBrowser-а оно по-другому :))
IF liThemeFlags >= 0
dwStyle := DWORD( _CAST, liThemeFlags )
ELSE
IF lEnable
dwStyle := _OR( STAP_ALLOW_NONCLIENT, STAP_ALLOW_CONTROLS, STAP_ALLOW_WEBCONTENT )
ELSE
dwStyle := STAP_ALLOW_NONCLIENT
ENDIF
ENDIF
SetThemeAppProperties( dwStyle )
// RETURN ( VerifyThemeState( ) ) // Для КаВо 28
RETURN ( IsThemeEnabled( ) ) // Для КаВо 27
2. Указание размеров габаритных элементов управления (контролов), типа ComboBox, при редактировании в ячейке.
Наверное, многие при правке-вводе данных через браузер использовали более «продвинутые» элементы редактирования. Класс bBrowser позволяет это делать.
Например: oColumn:ViewValueAs := #ComboBox
И я с удовольствием использую ComboBox. Есть и другие объёмные контролы, которые не помещаются в ячейку, распахиваясь при начале редактирования. Основной недостаток – невозможность уйти от стандартной высоты ( height=80 )…
(Внимание!!! Эта ситуация наблюдается только тогда, когда у Вас нет строки манифеста. Если добавить строку: «RESOURCE CREATEPROCESS_MANIFEST_RESOURCE_ID RC_RT_MANIFEST %appwizdir%\cctl6.man» – данная проблема проявляться не будет.)
Для выхода из этой ситуации лучший путь – переопределение.
Так я создал свой класс-наследник:
CLASS msBrowser INHERIT bBrowser
// Pencil 07.12.2007
В рамках него мы можем либо добавить управляющие переменные или напрямую изменить размеры создаваемых контролов. Я предвижу, что разработчики bBrowser через какое-то время сами что-то добавят. Поэтому не стал рисковать, прикинул обычные необходимые мне размеры и изменил их в методе напрямую:
METHOD EditCreate( iColumn, iRow, iRecNo, symClass, iStyle, uSpecial ) CLASS msBrowser
// Pencil 04.01.2010
**********************************
*** the piece was eaten by mice ***
*******
IF IsNil( iStyle )
// Pencil 22.06.2010
// Включил недостающую сортировку
// iStyle := WS_CHILD+WS_TABSTOP+CBS_AUTOHSCROLL
iStyle := WS_CHILD+WS_TABSTOP+CBS_SORT+CBS_AUTOHSCROLL
ENDIF
IF symClass = #ComboBox
// Pencil 22.06.2010
// Удлинил выпадающий список
// oEdit := ComboBox{SELF, 8001, Point{srcArea.Left, srcArea.Top}, Dimension{srcArea.Right-srcArea.Left, 80}, uSpecial, iStyle}
oPoint := Point{ srcArea.Left, srcArea.Top }
oDim := Dimension{ srcArea.Right - srcArea.Left, 200 }
oEdit := ComboBox{ SELF, 8001, oPoint, oDim, uSpecial, iStyle }
**********************************
*** the piece was eaten by mice ***
*******
3. Отсутствие обработки события выбора «мышкой» в элементе ComboBox (и ему подобных) при редактировании в ячейке.
Продолжим тему использования комбобокса в качестве элемента редактирования. Возможно, некоторые заметили, что если он раскроется вверх и перекроет собой заголовки колонок, то, после щелчка «мышки» (выбора подходящего пункта) список, конечно же, схлопнется. Но, артефакт на экране в виде прямоугольного пятна на заголовке колонок и выше (если список раскрылся и выше колонок) – останется. И будет таковым, если не сделать принудительное обновление окна или ещё раз не щёлкнуть на ячейку.
Как же так?
В CA-Visual Objects (и в версии 2.8 SP3) вообще отсутствует обработка события выбора «мышкой» элемента выпадающего списка. А разработчики bBrowser-а не стали этого поправлять в своей библиотеке. Это приводит к тому, что после щелчка «мышкой» работа продолжается, пока вы её не окончите, создав событие окончания редактирования.
Давайте исправлять:
METHOD EventHandler( oEvent ) CLASS msBrowser
// Pencil 13.02.2012
// Заставляем отрабатывать клик «мышкой» по записи в списке КомбоБокса:
// Клик мыши – означает выбор
// Ранее выбор определялся только нажатием клавиши ENTER
LOCAL hWnd AS PTR
LOCAL oChild AS OBJECT
IF oEvent:uMsg == WM_COMMAND
hWnd := PTR( _CAST, oEvent:lParam )
IF .NOT. hWnd == NULL_PTR
oChild := __WCGetControlByHandle( hWnd )
// Проверяем объект: КомбоБох ли это?
IF .NOT. oChild == NULL_OBJECT .AND.;
IsInstanceOf( oChild, #ComboBox )
// CBN_SELENDOK - Указывает, что выбор сделан в выпадающем списке,
// в то время как он был раскрыт. Он должен быть принят.
IF HiWord( oEvent:wParam ) == CBN_SELENDOK
// der Owner mu? das Event verarbeiten, da ansonsten bei einer
// Combobox nicht der Value richtig gesetzt wird
SELF:oCommandOwner:Dispatch( oEvent )
// Если редактируем
IF SELF:InEdit()
IF .NOT. SELF:EditClose() // Заканчиваем
SELF:EditCancel() // Иначе - прерываем ...
ENDIF
ENDIF
RETURN SELF:oCommandOwner:EventReturnValue
ENDIF
ENDIF
ENDIF
ENDIF
RETURN ( SUPER:EventHandler( oEvent ) )
4. Раскраска.
Что я под этим понимаю? Раскраска строк и колонок в браузере. Обычно это делается с помощью ColorCondition (ниже пример из описания bBrowser):
// define color condition and add it to the browser
oColorCondition := bColorCondition{"Server:BIRTHDAY<CToD('01/01/60')",;
odbsCUSTOMER,;
Color{COLORRED}}
oBrowser:ColorCondition:Add(oColorCondition)
Нас соблазняют простотой обращения к серверу, типа Server:BIRTHDAY, даже если это поле не описано. Что ж… Достаточно просто. Работает. Но, если полей много (больше трёх), если много разной сложной раскраски, то программа чудесным образом начинает «сыпаться». При этом ошибки «одна чудесней другой», так что сразу понять, где же «порылась собака» - трудно.
Так в чём же проблема? А проблема в том, что строковое выражение раскрывается макроподстановкой. Не верите – посмотрите в SDK! А в Ca-Vo макроподстановка имеет свои ограничения. Чьи тут проблемы – Ca-Vo или bBrowser – меня не интересует. Но, я бы порекомендовал разработчикам: пишите примеры честнее, не надо приучать «пользователей» к «плохому». Поля в серверах надо либо заранее описывать, либо использовать прямой метод FieldGet():
oColorCondition := bColorCondition{ "Server:FieldGet(#KOL) = 0 .AND. Server:FieldGet(#REZ) = 0", oBrowser:Server,, oBrush }
oBrowser:ColorCondition:Add( oColorCondition )
Если писать вот так, то ошибок не будет! Не зависимо от числа полей и степени сложности раскраски.
5. Неполная поддержка AdoRecordSet.
В bBrowser заявлена поддержка AdoRecordSet. Чтобы не быть голословным в своём утверждении – приведу маленький кусочек из SDK:
METHOD Use(oServer, auField, auOpen, auFormat) CLASS bBrowser
// =========================================================
// Beschreibung: Aktuellen Server entfernen und neuen Server setzen.
//
// Syntax: bBrowser:Use(oServer, [auField], [auOpen], [acHeader | onaFormat])
//
// Historie: 07.10.1998 JB Methode implementiert
// 09.12.2005 JB Vorhandene Spaltenobjekte werden zerstцrt.
// 10.02.2009 JB Optimierung implementiert, sodass der aktuelle Datensatz
// immer vollstдndig sichtbar ist.
// 21.09.2009 JB Beim ServerType #DBase wird der Filter vermerkt.
// 17.10.2009 JB Ermittlung des Status fьr die Infowerte korrigiert.
// =========================================================
**********************************
*** the piece was eaten by mice ***
*******
IF Empty(SELF:symServerType)
IF IsInstanceOf(SELF:oServer, #DBServer) .OR.;
(IsInstanceOf(SELF:oServer, #bCacheServer) .AND.;
IsInstanceOf(SELF:oServer:DataSource, #DBServer))
SELF:symServerType := #Dbase
ELSEIF IsInstanceOf(SELF:oServer, #SQLSelect) .OR.;
(IsInstanceOf(SELF:oServer, #bCacheServer) .AND.;
IsInstanceOf(SELF:oServer:DataSource, #SQLSelect))
SELF:symServerType := #SQL
ELSEIF IsInstanceOf(SELF:oServer, #ADOServer) .OR.;
(IsInstanceOf(SELF:oServer, #bCacheServer) .AND.;
IsInstanceOf(SELF:oServer:DataSource, #ADOServer))
SELF:symServerType := #SQL
ELSEIF IsInstanceOf(SELF:oServer, #ADORecordSet) .OR.;
(IsInstanceOf(SELF:oServer, #bCacheServer) .AND.;
IsInstanceOf(SELF:oServer:DataSource, #ADORecordSet))
SELF:symServerType := #SQL
ENDIF
ENDIF
**********************************
*** the piece was eaten by mice ***
*******
Как видим, AdoRecordSet (и не только) поддерживаются в качестве сервера для bBrowser.
Но, чтобы это заработало, мне пришлось «дорисовать» недостающие методы: DbStruct, Deleted, FieldPos, FieldSpec, FieldSym, GoBottom, GoTo, GoTop, LastRec, Notify, RecCount, RecNo, RegisterClient, ResetNotification, Skip, SuspendNotification, UnRegisterClient, Used.
Легко видеть, что здесь используются как устаревшие (LastRec), так и некоторые избыточные методы … Почему сделано так – не знаю. Мне было недосуг раздумывать «о высоком». Я сделал, как сделал. :))
bArrayServer.
1. Ошибки при создании индекса (ордера).
В основе bArrayServer-а лежит очень старая идея ArrayServer-а, т.е. возможность работы с массивом, как с обычным сервером. Просто и удобно. Но, здесь есть масса «подводных камней». И основная – то, что обычный массив очень не типизированная вещь. Абсолютно нет никаких гарантий, что в заявленном столбце будут однотипные данные. А ещё есть большой искус расширить список поддерживаемых типов. Например, в SDK bArrayServer видно, что авторы добавили типы «U» (неопределённый) и «O» (объект)… Но, если ты чего-то расширяешь, то готовь решение проблем.
Для создания ордера применяется метод CreateOrder(). Сортировка данных, как и следовало ожидать, делается с помощью функции ASort(). А в CA-Vo эта функция спотыкается, если предлагается сортировать данные с NIL, объекты или разнородные данные! Ну, и где защита от этого? Ау, разработчики!
Рассмотрим этот метод ещё немножко… Мы видим, что метод весьма похож на одноимённый из класса DbServer (и это правильно). Но, при создании ордера – ему не даётся имя!.. Т.е., даётся, но пустое. Зачем так? Давайте пока это спишем на забывчивость разработчика. Можно, конечно имя потом присвоить через метод OrderInfo() (к нему мы ещё вернёмся), но это не выход. Выход – переопределение метода. Поступаю, как обычно:
CLASS msArrayServer INHERIT bArrayServer
// Pencil 03.12.2008
// Для правки некоторых "приколов" bArrayServer-а
METHOD CreateOrder( cOrderName, uExpression, uForCondition, uWhileCondition,;
lDescend, cbEval, iInterval ) CLASS msArrayServer
// METHOD CreateOrder(uExpression, uForCondition, uWhileCondition,;
// lDescend, cbEval, iInterval) CLASS bArrayServer
// Pencil 10.12.2008
// Исправляем ошибки индексации
**********************************
*** the piece was eaten by mice ***
*******
// Даём имя ордеру
// SELF:auOrder[SELF:iOrderCount, BASOI_NAME] := ""
IF !IsNil( cOrderName ) .AND. IsString( cOrderName )
cOrderName := AllTrim( cOrderName )
cNameOrder := Upper( cOrderName )
ELSE
cNameOrder := ""
ENDIF
SELF:auOrder[ SELF:iOrderCount, BASOI_NAME ] := cNameOrder
**********************************
*** the piece was eaten by mice ***
*******
// FOR-Bedingung ausfьhren
IF .NOT. lForCondition .OR. Eval(uConditionBlock, SELF)
// aktuellen Datensatz einsortieren
iValueCount += 1
// Ловим ошибку заранее
// auValue[iValueCount, BASOI_KEYLIST_VALUE] := Eval(uExpressionBlock, SELF)
uValue := Eval( uExpressionBlock, SELF )
IF IsNil( uValue )
oError:Description := ;
"В индексе (" + cExpression + ") получется NIL-значение :" + CRLF +;
"нет указанного поля или NIL в данных."
BREAK oError
ENDIF
IF ValType( uValue ) == "O"
oError:Description := ;
"В индексе (" + cExpression + ") идёт сортировка объектов." + CRLF +;
"bArrayServer это не сортирует ..."
BREAK oError
ENDIF
auValue[ iValueCount, BASOI_KEYLIST_VALUE ] := uValue
**********************************
*** the piece was eaten by mice ***
*******
Вот, где-то так… А главное – ближе к синтаксису соответствующего метода DbServer-а.
2. Ошибки при удалении (очистки) ордера.
Тут тоже интересно. Информация об ордерах хранится в массиве. При попытке закрытия ордера, номер которого не попадает в диапазон значений в массиве – ничего не делается, но возвращается значение FALSE, что, в общем-то, правильно, но не очень неудобно. Потому, как с этим ордером ничего не происходит, и ничего мы с ним сделать не сможем. Более того, если номер ордера выходит за существующий диапазон – это явная ошибка, ошибка программиста. И я считаю, что в этом случае – надо возвращать TRUE.
METHOD ClearOrder( iOrderNo ) CLASS msArrayServer
// Pencil 13.10.2009
// Исправляем: если индекса нет, то значит, он очищен!
// angegebene Sortierung lцschen
IF SELF:iOrderCount = 0
// RETURN FALSE
RETURN TRUE
ELSEIF IsNumeric( iOrderNo )
IF iOrderNo = 0
iOrderNo := SELF:iOrderNo
ENDIF
IF .NOT. Between( iOrderNo, 1, SELF:iOrderCount )
// RETURN FALSE
RETURN TRUE
ENDIF
ENDIF
**********************************
*** the piece was eaten by mice ***
*******
Так мне нравится больше!
3. Ошибки при копировании в файл.
Здесь (в методе CopyToFile) меня не устраивают сразу три вещи:
- Есть начальная обработка ситуации, когда в качестве параметра передаются несуществующие поля. Но, это не доводится до конца – разработчик явно спешил.
- Не обрабатывается ситуация, когда в поле хранятся объекты (а bArrayServer это допускает!)
- При копировании в DBF не обрабатывается ситуация, когда этот файл должен быть в OEM-кодировке. Туда пишется только в ANSI!
Исправляем:
**********************************
*** the piece was eaten by mice ***
*******
FOR iPos:=1 UPTO iSize
IF IsNumeric(auFieldID[iPos]) .AND. auFieldID[iPos]>0 .AND.;
auFieldID[iPos] <= SELF:FCount
iField := auFieldID[iPos]
ELSEIF IsString(auFieldID[iPos])
iField := SELF:FieldPos(auFieldID[iPos])
ELSEIF IsSymbol(auFieldID[iPos])
iField := SELF:FieldPos(auFieldID[iPos])
ELSEIF IsInstanceOfUsual(auFieldID[iPos], #DataField)
iField := SELF:FieldPos(auFieldID[iPos]:Name)
ELSE
iField := 0
ENDIF
// Pencil 14.02.2009
***
// IF iField>0
// oDataField := SELF:aDataFields[iField]
// oFieldSpec := oDataField:FieldSpec
// IF Instr(oFieldSpec:ValType, "CDLN") .OR.;
// (oFieldSpec:ValType="M" .and. symFormat=#DBF)
//
// AAdd(auField, {iField, oFieldSpec:ValType,;
// oFieldSpec:Length, oFieldSpec:Decimals})
// ENDIF
// ENDIF
***
IF iField = 0
auTemp := { iField, "U", 0, 0 }
AAdd( auField, auTemp )
ELSE
oDataField := SELF:aDataFields[ iField ]
oFieldSpec := oDataField:FieldSpec
IF Instr( oFieldSpec:ValType, "CDLN" ) .OR.;
( oFieldSpec:ValType = "M" .AND. symFormat = #DBF )
AAdd( auField, { iField, oFieldSpec:ValType,;
oFieldSpec:Length, oFieldSpec:Decimals } )
ELSE
auTemp := { iField, "U", 0, 0 }
AAdd( auField, auTemp )
ENDIF
ENDIF
**********************************
*** the piece was eaten by mice ***
*******
ELSEIF symFormat=#DBF
// Pencil 30.01.2009
***
lAnsiDBF := odbsTarget:Info( DBI_ISANSI )
***
odbsTarget:SuspendNotification()
auValue := ArrayCreate(iFieldCount)
WHILE .NOT. SELF:EoF .AND. iRecCount>0 .AND.;
(IsNil(uWhileCondition) .OR. Eval(uWhileCondition, SELF))
IF IsNil(uForCondition) .OR. Eval(uForCondition, SELF)
FOR iField:=1 UPTO iFieldCount
// Pencil 14.02.2009
***
// auValue[iField] := SELF:FIELDGET(auField[iField, 1])
IF auField[ iField, 1 ] > 0
uValue := SELF:FIELDGET( auField[ iField, 1 ] )
// OEM, DOS
IF .NOT. lAnsiDBF .AND. auField[ iField, 2 ] == "C" .AND. IsString( uValue )
auValue[ iField ] := Ansi2Oem( uValue )
ELSE
auValue[ iField ] := uValue
ENDIF
ENDIF
***
NEXT
lDeleted := SELF:Deleted
// export current record
IF odbsTarget:Append()
FOR iField := 1 UPTO iFieldCount
// Pencil 14.02.2009
***
// odbsTarget:FIELDPUT(auField[iField, 1], auValue[iField])
IF auField[ iField, 1 ] > 0
odbsTarget:FIELDPUT( iField, auValue[ iField ] )
ENDIF
***
**********************************
*** the piece was eaten by mice ***
*******
Я понимаю, что Читатель ждёт от меня полные выкладки (листинг) моих переделок в методах. Это справедливо. Но, поверьте мне – текст получится огромным (он уже и так – немаленький)! А если Вы соображаете в CA-Vo, то Вам с лихвой хватит и тех намёток, что я предоставил. Так что, бог Вам в помощь! У Вас всё получиться!
4. Недоработка метода FieldPut.
Напоминаю, в ArrayServer-ах мы всё время боремся за соответствие данных заявленной структуре. Плюс, очень нежелательны значения NIL (если Вы собираетесь делать ордер).
Доделаем метод FieldPut:
oFieldSpec := SELF:FieldSpec( uField )
IF .NOT. oFieldSpec == NULL_OBJECT
cType := oFieldSpec:ValType
// Pencil 19.05.2012
***
// Контроль полученных типов
IF InList( cType, "C", "M", "D", "L", "N", "O" )
// Доводим uValue «до ума»
IF uValue == NIL
IF cType == "C" .OR. cType == "M"
uValue := NULL_STRING
ELSEIF cType == "D"
uValue := NULL_DATE
ELSEIF cType == "L"
uValue := FALSE
ELSEIF cType == "N"
uValue := 0
ELSEIF cType == "O"
uValue := NULL_OBJECT
ENDIF
ENDIF
5. Ошибки получения информации об ордере.
Это касательно метода OrderInfo(). Здесь у меня всего два замечания:
- В качестве параметра нельзя передать имя ордера (только номер!)
- При попытке узнать DBOI_NUMBER – возвращает не номер указанного ордера в списке ордеров, а номер текущего, что полностью расходится с работой аналогичного метода DbServer-а.
Займёмся в очередной раз переопределением метода:
METHOD OrderInfo( iInfoType, uReserved, uOrder, uValueNew ) CLASS msArrayServer
// Pencil 19.05.2012
// Исправляем работу метода OrderInfo
//METHOD OrderInfo(iInfoType, uReserved, iOrderNo, uValueNew) CLASS bArrayServer
//METHOD OrderInfo( kOrderInfoType, oFSIndex, uOrder, uOrdVal ) CLASS DbServer
//FUNCTION DBORDERINFO (nOrdinal,cBagName, uOrder, xNewVal)
LOCAL wOrder AS DWORD
LOCAL cOrder AS STRING
IF IsNil( uOrder )
wOrder := SELF:iOrderNo
// Учим работать со строковым именем поля
ELSEIF IsString( uOrder )
cOrder := AllTrim( uOrder )
IF .NOT. cOrder == NULL_STRING
cOrder := Upper( cOrder )
wOrder := AScan( SELF:auOrder, { |x| x[ BASOI_NAME ] == cOrder } )
ELSE
wOrder := 0
ENDIF
ELSEIF IsLong( uOrder ) .AND. Between( uOrder, 1, SELF:iOrderCount )
wOrder := uOrder
ELSE
wOrder := 0
ENDIF
// Исправляем ошибку с DBOI_NUMBER (когда почему-то возвращается SELF:iOrderNo,
// т.е. текущий, а не указанный ордер ...)
IF iInfoType == DBOI_NUMBER
RETURN wOrder
ENDIF
RETURN ( SUPER:OrderInfo( iInfoType, uReserved, wOrder, uValueNew ) )
Ура! Вы даже себе не представляете, как я устал, пока всё это написал. Знал бы, что потрачу столько времени – наверное, плюнул бы и отказался. Ан нет – дописал. Вот какой я молодец! Надеюсь, Вам это пригодится. Осталось добавить: сам себя не похвалишь – никто не похвалит!
20.05.2012 г. Карандаш.