Как добавить свой класс Header в Grid |
> Получаю код формы содержащей GRID
с несколькими полями.
> Создаю класс, генерирую prg-файл Class Browser-ом.
> Запускаю - синтаксическая ошибка в строке
(GRID.COLUMN1.HEADER1.. as HEADER )
> ... и никто помочь не может. Одна надежда на Вас.
Давайте попробуем разобраться, разложив вопрос на следующие пункты:
Попробуйте изменить код на подобный следующему:
*///////// any.prg for VFP 6.0+SP3 or later RELEASE oFrm PUBLIC oFrm oFrm = CreateObject('form1') IF VARTYPE(oFrm) # 'O' RELEASE oFrm RETURN .F. ENDIF oFrm.Show() DEFINE CLASS form1 AS form Top = 0 Left = 0 Height = 212 Width = 218 DoCreate = .T. Caption = "Form1" Name = "Form1" DataSession = 2 ADD OBJECT grdsetup AS grid WITH ; ColumnCount = 2, ; Height = 200, ; Left = 12, ; RecordSource = "setup", ; RecordSourceType = 1, ; Top = 0, ; Width = 192, ; Name = "grdSetup", ; Column1.ControlSource = "setup.key_name", ; Column1.Name = "Column1", ; Column2.ControlSource = "setup.value", ; Column2.Name = "Column2" PROCEDURE Load ThisForm.AddObject('oSE', 'SessionDE', 'grdwithcolDE') IF TYPE('ThisForm.oSE') # 'O' OR ISNULL(ThisForm.oSE) RETURN .F. ENDIF ENDPROC PROCEDURE Init() WITH ThisForm.grdsetup.Column1.header1 .Caption = "key_name" ENDWITH WITH ThisForm.grdsetup.Column2.header1 .Caption = "value" ENDWITH ENDPROC ENDDEFINE DEFINE CLASS grdwithcolDE AS DataEnvironment name = "Dataenvironment" ADD OBJECT Cursor1 AS Cursor WITH ; name = "Cursor1", ; alias = "setup", ; cursorsource = "setup", ; database = HOME(2)+"tastrade\data\tastrade.dbc" ENDDEFINE DEFINE CLASS SessionDE As Session DataSession = 2 Name = "SessionDE" oDE = NULL PROTECTED FUNCTION CreateDE(tcClassNameDE) IF VARTYPE(tcClassNameDE) # 'C' OR EMPTY(tcClassNameDE) RETURN .F. ENDIF WITH This IF .DataSession <> 1 SET DATASESSION TO .DataSessionId SET DELETED ON SET MULTILOCKS ON SET EXCLUSIVE OFF ENDIF .oDE = NewObject(tcClassNameDE) IF VARTYPE(.oDE) = 'O' .oDE.OpenTables() ELSE RETURN .F. ENDIF ENDWITH ENDFUNC PROCEDURE Init(tcClassNameDE) RETURN This.CreateDE(tcClassNameDE) ENDPROC PROCEDURE Destroy() This.oDE = NULL ENDPROC ENDDEFINE *///////// end of any.prg
т.е. просто перенесите инициализацию заголовков/колонок в событие MyForm.Init()
Аналогичный вопрос довольно часто задаётся также в связи с тем, что имеется желание перепрограммировать его событие Click(). И действительно, имеется проблема, поскольку класс Header не может быть добавлен в vcx-библиотеку, по крайней мере официально :-) ... Давайте рассмотрим это дело в двух аспектах:
Добавить свой класс из кода - нет проблем! И чтобы не быть голословным приведу коротенький пример того как это можно сделать.
*//////////////////////// any.prg for VFP 6.0+SP3 or later RELEASE oFrm PUBLIC oFrm oFrm = CreateObject('form1') IF VARTYPE(oFrm) # 'O' RELEASE oFrm RETURN .F. ENDIF oFrm.Show() DEFINE CLASS form1 AS form Top = 0 Left = 0 Height = 212 Width = 218 DoCreate = .T. Caption = "Form1" Name = "Form1" DataSession = 2 PROCEDURE Load * *-- SessionDE ThisForm.AddObject('oSE', 'SessionDE', 'GridDE') IF TYPE('ThisForm.oSE') # 'O' OR ISNULL(ThisForm.oSE) RETURN .F. ENDIF * *-- Grid ThisForm.AddObject('oGrid', 'mygrid') IF TYPE('ThisForm.oGrid') # 'O' OR ISNULL(ThisForm.oGrid) RETURN .F. ENDIF * *-- Columns LOCAL lnCol, lcCurObj, lcAlias, lcSetComp lcSetComp = SET('COMPATIBLE') IF INLIST(lcSetComp, 'ON', 'DB4') SET COMPATIBLE OFF ENDIF lcAlias = PROPER(ThisForm.oSE.oDE.Cursor1.alias) WITH ThisForm.oGrid .RecordSource = lcAlias FOR lnCol = 1 TO FCOUNT(lcAlias) lcCurObj = 'Column'+LTRIM(STR(lnCol)) .AddObject(lcCurObj, 'MyColumn') IF TYPE('ThisForm.oGrid.'+lcCurObj) # 'O' RETURN .F. ENDIF lcCaption = PROPER(FIELD(lnCol, lcAlias)) lcField = lcAlias+'.'+lcCaption WITH .Columns(lnCol) .Header1.Caption = lcCaption .ControlSource = lcField .Width = FSIZE(lcCaption, lcAlias)*7 .Text1.Visible = .T. .Visible = .T. ENDWITH ENDFOR .Visible = .T. ENDWITH IF !(lcSetComp == SET('COMPATIBLE')) SET COMPATIBLE &lcSetComp ENDIF ENDPROC PROCEDURE Init WITH This .Width = 2*SYSMETRIC(21)/3 .Height = 2*SYSMETRIC(22)/3 .Resize() ENDWITH ENDPROC PROCEDURE Resize WITH ThisForm.oGrid LOCAL lnWs, lnHs lnWs = .Left+.Left lnHs = .Top+.Top IF ThisForm.Width > lnWs .Width = ThisForm.Width - lnWs ENDIF IF ThisForm.Height > lnHs .Height = ThisForm.Height - lnHs ENDIF ENDWITH ENDPROC ENDDEFINE DEFINE CLASS MyHeader AS Header PROCEDURE Click() WITH This.Parent.Parent .ChangeOrder(.ActiveColumn) ENDWITH ENDPROC ENDDEFINE DEFINE CLASS MyColumn AS Column Width = 30 ADD OBJECT Header1 AS MyHeader ENDDEFINE DEFINE CLASS MyGrid AS grid ColumnCount = 0 DeleteMark = .F. Height = 200 RecordSourceType = 1 Width = 192 Name = "MyGrid" PROCEDURE ChangeOrder LPARAMETERS tnCol IF EMPTY(tnCol) RETURN .F. ENDIF LOCAL lcAlias, lcCdx, lnRecNo, lcSource; ,lcField, lnPos, lnTag, lbFound WITH This * *-- Check CDX-file lcAlias = .RecordSource lcCdx = CDX(1, lcAlias) IF EMPTY(lcCdx) WAIT WINDOW 'CDX file for '+lcAlias; +' not found.' NOWAIT RETURN .F. ENDIF IF EMPTY(TAG(lcCdx, 1, lcAlias)) RETURN .F. ENDIF * *-- Find index for column lcSource = .Columns(tnCol).ControlSource lnPos = ATC('.', lcSource) lcField = UPPER(SUBSTR(lcSource, lnPos+1)) IF LENC(lcField) > 10 lcField = LEFTC(lcField, 10) ENDIF FOR lnTag = 1 TO TAGCOUNT(lcCdx, lcAlias) IF TAG(lcCdx, lnTag, lcAlias) = lcField lbFound = .T. EXIT ENDIF ENDFOR IF !lbFound WAIT WINDOW 'Tag: '; +lcField+' not found.' NOWAIT RETURN .F. ENDIF * *-- Change order lnRecNo = IIF(!EOF(), RECNO(), 0) SET ORDER TO TAG (lcField) ; IN (lcAlias) .ActivateCell(1, tnCol) IF lnRecNo # 0 GO (lnRecNo) ELSE GO TOP ENDIF .Refresh() ENDWITH ENDPROC ENDDEFINE DEFINE CLASS GridDE AS DataEnvironment name = "Dataenvironment" ADD OBJECT Cursor1 AS Cursor WITH ; name = "Cursor1", ; alias = "customer", ; cursorsource = "customer", ; database = HOME(2); +"tastrade\data\tastrade.dbc" ENDDEFINE DEFINE CLASS SessionDE As Session DataSession = 2 Name = "SessionDE" oDE = NULL PROTECTED FUNCTION CreateDE(tcClassNameDE) IF VARTYPE(tcClassNameDE) # 'C' ; OR EMPTY(tcClassNameDE) RETURN .F. ENDIF WITH This IF .DataSession <> 1 SET DATASESSION TO .DataSessionId SET DELETED ON SET MULTILOCKS ON SET EXCLUSIVE OFF ENDIF .oDE = NewObject(tcClassNameDE) IF VARTYPE(.oDE) = 'O' .oDE.OpenTables() ELSE RETURN .F. ENDIF ENDWITH ENDFUNC PROCEDURE Init(tcClassNameDE) RETURN This.CreateDE(tcClassNameDE) ENDPROC PROCEDURE Destroy() This.oDE = NULL ENDPROC ENDDEFINE *//////////////////////// end of any.prg
Как видите всё настолько просто, что практически нечего комментировать :-) Разве только нужно сказать, что я воспользовался своей утилитой GenDE (см. на стр. Примеры), чтобы получить исходник класса GridDE :-))...
Выше было сказано, что "класс Header не может быть добавлен в vcx-библиотеку, по крайней мере официально". Давайте обсудим теперь эту проблему, т.е. посмотрим, как же это можно сделать т.с. неофициально :-)
Определения классов GridDE, SessionDE, MyColumn и MyHeader помещается в prg-файл (HeaderInGrid_p.prg), однако, кроме эстетических соображений это вызывает проблему использования таких классов как DE-классов в формах, и вот почему:
Суть проблемы в том, что метод AnyObj.AddObject() не позволяет создавать экземпляры классов из prg-файлов непосредственно (только из vcx-библиотек совместно с SET CLASSLIB TO ...), в то время как только именно метод AddObject() корректно работает с Private Data Session формы, в отличии от CreateObject()/NewObject() [см. тему "Имею VFP 6.0+SP3 VS6 и сейчас появился класс Session. Не покажете ли пример его использования?" в FAQ на http://vfpdmur.narod.ru/faq/de_r.html]
Возможный путь обхода указанной трудности заключается в том, чтобы поместить класс SessionDE (производный от Session) в vcx-библиотеку. Однако и здесь, сделать это непосредственно не удаётся, т.к. возникает ошибка: Cannot visually modify a class of this type (Error 1978). Последнее справедливо также и для классов MyColumn, MyHeader :-() ...Тем не менее, имеется небольшой трюк, чтобы осуществить это. Ниже объясняются шаги, чтобы сделать это, на примере классе SessionDE из библиотеки GridLib.vcx
Если Вы случайно попытались редактировать нередактируемый класс Session (Column, Header) т.е. получили ошибку Error 1978, лучше закрыть среду VFP и открыть её снова, поскольку такая попытка редактирования приводит к exclusive-захвату файла Вашей библиотеки и никакими силами избавиться от такого захвата не удаётся :-( Надёжным признаком возможности редактирования класса SessionDE в Class Designer является наличие у него иконки класса Custom на закладке Classes в проекте. И наоборот, не пытайтесь открыть на редактирование в Class Designer класс, у которого отсутствует иконка (т.е. производный от Session), это неизбежно приведёт к ошибке Error 1978.
Я был бы не до конца честным по отношению к Вам, если бы не раскрыл одну пикантную подробность :-) Имеется определённая сложность в создании класса, производного от Column. Проблема заключается в том, что он т.с. по определению является контейнером, включающем в себя класс Header. Таким образом, если мы не изловчимся включить свой класс (MyHeader) в создаваемый нами класс, производный от Column, т.е. в точности то, что делает код:
DEFINE CLASS MyColumn AS Column ADD OBJECT Header1 AS MyHeader ENDDEFINE
то мы будем вынуждены во время выполнения, где-то в событии MyColumn.Init() сделать следующее:
WITH This IF TYPE('This.Header1') = 'O' .RemoveObject('Header1') ENDIF .AddObject('Header1', 'MyHeader') ENDWITH
удаляя вставленный по умолчанию класс Header1 и добавляя вместо него свой класс MyHeader :-( ...Именно так и написан класс MyColumn из GridLib.vcx. Это конечно не шедевр, однако нет почти никаких проблем по его редактированию, ну за исключением тех, что указаны в замечании выше.
Теперь, когда как надеюсь проблема понятна, можно создать и т.с. полноценный класс-контейнер, как производный от Column. Для этого можно поступить следующим образом:
Наконец, чтобы легко можно было изменять используемый класс с MyColumn на NewColumn и контролировать какой же из классов используется я поместил в событие MyForm.Load() следующий код:
* *-- What class column we are used now? #DEFINE UDCHEADER .F. #IF UDCHEADER #DEFINE CLS_COLUMN 'NewColumn' &&- includes the user defined class Header #ELSE #DEFINE CLS_COLUMN 'MyColumn' &&- includes the default class Header #ENDIF WAIT WINDOW 'Used: '+CLS_COLUMN NOWAIT
В заключении хочу посоветовать: прежде чем начать чего-нибудь делать с библиотекой GridLib.vcx, сделайте её резервную копию :-)
Из кода/vcx и следует создавать свой класс MyPageFrame с Page-ами... а уже его и добавлять. :-)
HeaderInGrid_p.prg | - "прямой" код, т.е. определение всех классов одновременно и исполнимый пример |
GridLib.vcx | - создана по описанной выше технологии для классов из HeaderInGrid_p.prg |
HeaderInGrid_l.prg | - код, использующий классы из библиотеки GridLib.vcx |
Загрузить Hdrgrd.zip (17,7KB) (см. также замечание в начале стр. Примеры, относительно медленной скорости загрузки :-()
На примере класса, производного от Header, показана возможность изменения порядка сортировки по щелчку на заголовке столбца в Grid-е. Загрузить grdhdrcl.zip (12,0KB)