20.07.2009, 16:03 | #1 |
Участник
|
Итератор с поддержкой методов обратного вызова для обработки контролов на форме
Для изменения свойств контролов на форме есть два подхода: либо править свойства в дизайне формы, либо делать это откуда-то из кода. Первый способ вроде бы наиболее простой, но он, порой, связан со значительными трудозатратами, кроме того, таким образом нельзя править свойства контролов, входящих в группы со свойством AutoDataGroup == Yes. При этом отказываться от этого чудесного свойства тоже не хотелось бы, поскольку оно позволяет за счет простого изменения группы полей на таблице менять перечень и порядок полей в группах на всех связанных формах.
С другой стороны, чтобы менять свойства контролов из кода, нужно обращаться к ним по одному, при этом контролы обычно должны иметь свойство AutoDeclaration == Yes, а для этого у группы, в которую входят контролы, необходимо обязательно выставить AutoDataGroup == No. Как вариант, можно в коде реализовать цикл обхода всех дочерних контролов той же группы, смотреть, к какому типу относится тот или иной дочерний контрол (потому что у наследников FormControl ряд свойств отличается, а в самом FormControl кое-какие свойства просто не реализованы, поэтому необходмо приведение типов дочерних контролов), тогда для группы можно оставить свойство AutoDataGroup == Yes. Но каждый раз организовывать такой цикл очень нудно: на одной и той же форме контролы в разных группах может понадобиться обрабатывать совершенно по-разному. Например, есть группы, в которые выведены коды пользователей либо сотрудников, а также display-методы для отображения их имен из таблицы сотрудников (EmplTable), плюс дата и время создания/модификации записи. Вот как это "штатно" выглядит: Видно, что для кодов пользователей и сотрудников (userId/emplId) значения свойств displayLength у расширенных типов отличаются, а для имен и значений времени тут совершенно излишни метки. Хотелось бы, чтобы эти группы выглядели примерно так: Так вот (та-да!) теперь этого можно легко добиться за счет использования итератора и методов обратного вызова, которые собственно будут устанавливать свойства контролов. В коде на форме это выглядит примерно так: X++: Set setOfIds; ; setOfIds = DEV_FormHelpers::addStringCtrlId2Set(); // на контролах какого типа должен дергаться метод обратного вызова DEV_iterateThroughFormControls( GrpAuthorNames, null, staticmethodstr(DEV_FormHelpers, hideLabel), setOfIds, true, classnum(DEV_FormHelpers) ); DEV_iterateThroughFormControls( GrpAuthorIds, null, staticmethodstr(DEV_FormHelpers, setColumnWidth), setOfIds, true, classnum(DEV_FormHelpers) ); setOfIds = DEV_FormHelpers::addTimeCtrlId2Set(); DEV_iterateThroughFormControls( GrpCreatedModifiedTime, null, staticmethodstr(DEV_FormHelpers, hideLabel), setOfIds, true, classnum(DEV_FormHelpers) ); Может возникнуть резонный вопрос, почему все-таки не изменить дизайн формы вручную раз и навсегда. Что ж, мне лично зачастую приходится создавать схожие по внешнему виду формы, отличающиеся некоторыми параметрами поведения и обрабатываемыми данными. Разумеется, лепить кучу форм-клонов неохота - куда интересней сделать одну форму и семейсво классов, управляющих ее поведением. Так вот, для изменения внешнего вида формы в зависимости от класса, который ей управляет, и понадобился такой механизм. Метод-итератор для рекурсивного перебора контролов на форме использует интерфейс DEV_IFormContainerControl, представляющий общие методы контролов-контейнеров (FormButtonGroupControl, FormGridControl, FormGroupControl, FormMenuButtonControl, FormTabControl, FormTabPageControl и - с одной оговоркой - FormDesign). Также в проект включены и другие вспомогательные интерфейсы - DEV_IFormButtonControl (общие методы и свойства FormButtonControl, FormCommandButtonControl и FormFunctionButtonControl) и DEV_IFormGridContainedControl (общие методы и свойства контролов, которые могут появиться на гриде при использовании табличных групп полей). Единственное неудобство использования этих интерфейсов - это необходимость при присваивании "интерфейсным" переменным ссылок на наследники FormControl использовать промежуточное приведение к Object. Впрочем, избавление от необходимости приведения того же FormControl к нужному типу его наследника, чтобы дернуть какой-нибудь метод, не определенный на FormControl, и унификация работы со схожими типами контролов, на мой взгляд, с лихвой компенсирует это неудобство. PS. Все это проверялось на AX3 |
|
|
За это сообщение автора поблагодарили: mazzy (2), sukhanchik (6), Logger (15), Ar (1), konopello (3), Jorj (1), Stainless (1), Kabardian (6). |
20.07.2009, 18:22 | #2 |
Участник
|
Цитата:
Вообще-то не должны. Для того, чтобы обатиться к контролу, у которого св-во AutoDeclaration установлено в No (например он входит в группу, и его св-ва недоступны) есть 2 способа. 1-ый. Работатет не для всех св-в, но не может породить run-time ошибки FormDataSource.object(fieldId).visible(), enabled(), и некоторые другие. 2-ый. Предоставляет доступ ко всем св-вам, но может породить ошибку run-time formStringControl = element.control(control::"имя контрола на форме"); |
|
|
За это сообщение автора поблагодарили: MerkurievV (0). |
20.07.2009, 19:20 | #3 |
Участник
|
Цитата:
Сообщение от petr
Для того, чтобы обатиться к контролу, у которого св-во AutoDeclaration установлено в No (например он входит в группу, и его св-ва недоступны) есть 2 способа.
1-ый. Работатет не для всех св-в, но не может породить run-time ошибки FormDataSource.object(fieldId).visible(), enabled(), и некоторые другие. |
|
|
За это сообщение автора поблагодарили: MerkurievV (0). |
28.09.2009, 18:23 | #4 |
Участник
|
В ряде случаев из-за кое-каких недотестированных доработок потребовалась возможность оперативно выяснять на формах, какие «на самом деле» суммы содержатся в отображаемых полях типа real. К примеру, забыли где-нить в импорте сумму округлять перед записью в поле таблицы, а потом - пойди, разберись, почему что-то не работает, ведь FormRealControl по умолчанию показывает значения с точностью лишь до двух знаков. Так вот, коллега на базе выложенного итератора реализовал возможность увидеть вещественные значения с большей точностью, приделав кнопку «Увеличить точность» к форме пользовательской настройки (SysSetupForm). На кнопке дергается такой код:
X++: void clicked() { super(); infolog.startLengthyOperation(); DEV_iterateThroughFormControls( sysSetupForm.getFormRun().design(), element, // объект, чей метод обратного вызова будет задействован @"DEV_setRealEditPrecision", // название метода обратного вызова на форме SysSetupForm DEV_FormHelpers::addRealCtrlId2Set() ); infolog.endLengthyOperation(); element.close(); // показалось удобнее сразу закрывать форму настроек } X++: // устанавливает для контролов RealEdit точность отображения минимум 6 знаков #define.NewNoOfDecimals (6) public void DEV_setRealEditPrecision(FormRealControl _formControl) { if (!( _formControl.noOfDecimalsMode() == AutoMode::Fixed && _formControl.noOfDecimalsValue() > #NewNoOfDecimals )) { _formControl.noOfDecimals( #NewNoOfDecimals, AutoMode::Fixed ); } } |
|
|
За это сообщение автора поблагодарили: Logger (9), sgt.Pepper (1). |
28.09.2009, 22:55 | #5 |
Участник
|
Цитата:
т.е. возможны случаи когда мы работаем через енум Control, но не можем использовать AutoDeclaration ? |
|
29.09.2009, 11:30 | #6 |
Участник
|
Дело в том, что свойство AutoDeclaration, насколько я могу судить, действует на FormXXControl'ы, но не на FormBuildXXControl'ы. Более того, если попытаться даже присвоить переменной типа FormBuildXXControl ссылку на элемент управления с AutoDeclaration == Yes, то такой код просто не скомпилируется по причине несовместимости типов. Соответственно, если требуется на форме до вызова super() в init() получить ссылку на какой-либо FormBuildXXControl, скажем, чтобы передать ее параметром в класс, то тут AutoDeclaration никак не поможет.
Вот пример из моего скромного опыта: есть «многофункциональная» форма, работающая на базе строк журналов. Многофункциональность заключается в том, что в зависимости от параметров вызова эта форма должна показывать немного различающиеся наборы полей и реализовывать немного различающуюся логику. Для этого ею рулит семейство классов, каждый из которых (наследник от базового) реализует эти незначительные отличия. И вот встает задача: в зависимости от параметров вызова, кроме прочего, показывать на гриде формы разные наборы полей, причем для простоты каждый набор задан группой полей на соотв. таблице. Варианты были такие:
X++: element.form().design().control( Control::GrpGridFields ); |
|
29.09.2009, 12:17 | #7 |
----------------
|
простите за вопрос.
На Dimension-полях работает? |
|
29.09.2009, 13:13 | #8 |
Участник
|
|
|
02.10.2009, 10:56 | #9 |
Участник
|
Небольшое исправление к коду для 4.0:
DEV_FormHelpers X++: private static Set getSetOfClassIds(Set _set) { classId locClassId; Set ret; ; if (_set) { if (_set.typeId() != typeof(locClassId)) { //callStack2Infolog(); // for 4.0 info(con2str( xSession::xppCallStack() )); throw error( error::wrongUseOfFunction( funcname() ) ); } ret = _set; } else { ret = new Set( typeof(locClassId) ); } return ret; } |
|
14.09.2010, 11:04 | #10 |
Участник
|
Во вложении - вспомогательный класс для итератора, обновленный для использования в AX 2009.
PS. Для себя я сделал класс DEV_FormHelpers используемым в итераторе по умолчанию в тех случаях, когда в качестве метода обратного вызова указывается статический, а не экземплярный метод (т.е. _callbackObject == null): X++: public static client boolean DEV_iterateThroughFormControls( Object _parentControl, Object _callbackObject, identifiername _callbackObjectMethodName, Set _setOfClassIds2InvokeOn, boolean _recursive = true, classId _staticCallbackMethodClassId = classnum(DEV_FormHelpers) ) Последний раз редактировалось gl00mie; 14.09.2010 в 11:40. Причина: typo... |
|
15.09.2010, 08:33 | #11 |
Участник
|
вопрос из другой ветки Как глобально отключить автоопределение ширины столбца = autoSizeColumns(false) ?
а для каких задач может использоваться данный итератор? |
|
15.09.2010, 10:55 | #12 |
Участник
|
Собственно, большинство решаемых задач описаны в этой ветке:
|
|
15.09.2010, 11:29 | #13 |
Участник
|
Цитата:
Сообщение от gl00mie
[/LIST]В общем, я лично с помощью итератора решаю в основном те задачи, которые при "традиционном" подходе требовали бы работы с каждым из заранее известного множества контролов в каждой из заранее известного множества форм, при том что эти множества на момент разработки могут быть еще неизвестны. "Традиционный" подход в таких случаях требует, как правило, слишком большого объема ручной работы, отказа от использования "автогрупп" контролов на основе табличных групп полей, внесения избыточных модификаций в стандартные формы (как с теми же гридами в AX 2009), плюс ко всему он еще и удорожает сопровождение сделанных модификаций.
Это же противоречит объектно-ориентированному подходу и полностью нарушает инкапсюляцию. Говоря в терминологии C++, ты создаешь класс, который является френдом очень многих других классов. Эдакий супер-френд. Со всеми вытекающими последствиями, которые вроде обсуждались у программистов С++. Например, чтобы выявить особенности поведения, тебе недостаточно понять как работает сам объект и его класс, тебе нужно понять как работают и все френды. Разве не так? |
|
15.09.2010, 12:38 | #14 |
Участник
|
Цитата:
PS. Сперва очень долго не мог понять, при чем тут вообще SysSetupForm - мне и в голову не могло прийти совать в него код, специфичный для особенностей дизайна какой-то одной конкретной формы. Последний раз редактировалось gl00mie; 15.09.2010 в 12:42. |
|
15.09.2010, 13:42 | #15 |
Роман Долгополов (RDOL)
|
Цитата:
Сообщение от gl00mie
А откуда взялось мнение, что всё вышеописанное реализуется в SysSetupForm? Там реализована только кнопка увеличения точности отображения, которая может быть применена к любой форме. Если же мне нужно переделать свойства пары контролов на форме, и я не хочу их для этого выдергивать из "автогруппы", то я просто дописываю метод на самой этой форме либо в классе управления этой формой, который при ее инициализации с помощью итератора меняет эти свойства.
Но пара контролов на форме это пушкой по воробьям. Чем element.design().controlName() то не угодил? Вроде прекрасно достает контролы из автогрупп. Или уже не достает и я отстал от жизни? |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
16.09.2010, 09:37 | #16 |
Участник
|
Цитата:
Реализуется метод в форме. Но вызов то идет из SysSetupForm. Это и есть friend. Т.е. либо метод надо делать публичным, либо френдить SysSetupForm. Поэтому SysSetupForm получается френдом для очень многих форм (другими словами, SysSetupForm слишком много знает о деталях реализации других форм), что приводит к снижению инкапсюляции. (пусть в Аксапте нет ключевого слова friend. но смысл то и проблемы не меняются) Цитата:
Если определяется класс, который не реализует математических объектов вроде матриц или комплексных чисел и не является типом низкого уровня наподобие связанного списка, то:
[а] Не используйте глобальных данных. [b] Не используйте глобальных функций (не членов). [c] Не используйте общих данных-членов. [d] Не используйте функции friend (но только для того, чтобы избежать [а], [b] или [c]). [e] Не обращайтесь к данным-членам другого объекта непосредственно. [f] Не заводите в классе "поле типа"; используйте виртуальные функции. [g] Используйте функции-подстановки только как средство значительной оптимизации. ... Отметим, что общие базовые классы и друзья (friend) являются частью общего интерфейса класса (см. $$5.4.1 и $$12.4). Последний раз редактировалось mazzy; 16.09.2010 в 10:04. Причина: добавил цитаты из Страуструпа |
|
16.09.2010, 14:39 | #17 |
Роман Долгополов (RDOL)
|
Да нет там никаких таких вызовов (Функционал "расширения сознания" не в счет - он на своем месте). Все остальные специфичные для отдельных форм вещи реализуются прямо на этих отдельных формах
|
|
|
За это сообщение автора поблагодарили: tricky (1). |
14.03.2011, 21:15 | #18 |
Участник
|
Поддержка FormBuild-контролов и опционального параметра для метода обратного вызова
Понадобилось тут подпилить один кусок кода в стандартном приложении, в связи с чем в итератор и вспомогательный класс DEV_FormHelpers была добавлена поддержка FormBuild-контролов и опционального параметра, который может передаваться в вызываемый из итератора метод обратного вызова. Через этот параметр можно организовать общение метода с вызывающим итератор кодом без заведения глобальных переменных. Во вложении - обновленный метод Global (итератор), вспомогательный класс и вспомогательные интерфейсы, которые раньше были выложены отдельно.
PS. Теперь DEV_FormHelpers является классом по умолчанию, чьи статические методы обратного вызова используются, если не задана ссылка на объект, реализующий метод обратного вызова, соотв., не обязательно указывать идентификатор этого вспомогательного класса в вызове итератора. Последний раз редактировалось gl00mie; 14.03.2011 в 21:17. |
|
|
За это сообщение автора поблагодарили: mazzy (2), Logger (2), wojzeh (1). |
06.08.2013, 22:16 | #19 |
Участник
|
__________________
Felix nihil admirari |
|
Теги |
design, form, formreferencegroupcontrol, дизайн, законченный пример, итератор, округление, полезное, форма |
|
|