NAV4U (онлайн-журнал) » Blog Archive » Page Application Worksheet (Журнал Применения). Редизайн с использованием элементов Part в качестве SubPage

Page Application Worksheet (Журнал Применения). Редизайн с использованием элементов Part в качестве SubPage

В данной статье хотелось бы привести простейший, но от этого не менее полезный пример того, как можно применить Add-in в NAV 2009 SP1. Что это такое Add-in’ы и как они помогают расширить функциональность RTC-клиента, говорилось в 9-м выпуске Журнала.

Немного предыстории…

В NAV 5.0 появился достаточно интересный инструмент, называемый «Application Worksheet» - «Журнал Применения».

 Application Worksheet

Основное назначение данной формы заключается в том, чтобы предоставить пользователю единый интерфейс, используя который, он может осуществлять ручное применение/расприменение учтенных покупочных/продажных операций.

Казалось бы, все просто и красиво: форма, сабформа со списком примененных операций и сабформа со списком операций, доступных для применения. Основные кнопки: Применение, Удалить Применение.

А теперь взглянем на то, как реализована форма Журнала Применения в версии NAV 2009 SP1:

Журнал применения в Microsoft Dynamics NAV 2009 SP1

Как видим, отличие кардинальное: во-первых, нет сабформы с примененными операциями, во-вторых, нет сабформы с операциями, доступными для применения. Обе эти сабформы были заменены формами, вызываемыми через View -> Applied Entries (F11) и View -> Select Additional Entries to Apply (Ctrl+F11), соответственно.

Расприменение операций происходит по нажатию на кнопку Remove Application. Применение доступной операции произойдет по нажатию кнопки ОК:

Remove Application

К слову, как сабформы в NAV 5.0, так и формы в NAV 2009 SP1 представляют собой один объект: Form 522 «Applied Item Entries Temp» и Form 522 «View Applied Entries», соответственно.

Дизайн «Журнала Применения» в RTC полностью соответствует дизайну формы классического 2009 клиента:

Журнал применения в ролеориентированном клиенте

Как мне представляется, вариант версии 2009 SP1 смотрится хуже, нежели его аналог в предшествующей 5-й версии: пользователь явно должен делать больше действий, открывая/закрывая и без того достаточно медлительные (будем надеяться, пока) Pages.

Попробуем разобраться, в чем же кроется причина подобной реализации «Журнала Применения» в NAV 2009 SP1…

  • Во-первых, начиная с 2009-й версии, появился такой тип объектов как Page.
  • Во-вторых, Microsoft придерживается принципа, что дизайн объектов Form и Page должен быть идентичным.
  • В-третьих, объект типа Page не имеет аналога триггера OnAfterGetCurrRecord() объектов типа Form. Причины этого немного затронуты здесь, хотя и не слишком убедительно.

Собственно, требования сходства дизайна и техническая реализация Pages на уровне ядра системы, привели к тому, что, казалось бы, удачная в техническом плане реализация «Журнала Применения», претерпела кардинальные изменения в 2009 SP1 версии.

Посмотрим, как реализована основная логика работы «Журнала Применения» в NAV 5.0:

OnAfterGetCurrRecord

Заключается она в выполнении триггера Form - OnAfterGetCurrRecord(). Когда пользователь перемещается по форме, активируя очередную запись, срабатывает данный триггер. В нем прописана логика выполнения функций сабформ, которые, в свою очередь, наполняют сами сабформы записями временной 32-й таблицы (товарная книга). В сабформах отображаются либо уже примененные товарные операции (примененные к активированной товарной операции главной формы), либо товарные операции, доступные для применения. Все предельно просто.

Я был бы рад показать Вам АНАЛОГИЧНУЮ логику в версии 2009 SP1, но ее там просто нет… Она, конечно, присутствует, но не на триггере, а в Action’е. (см. рисунок выше).

Сделаем так, чтобы дизайн пейджа «Журнал Применения» был идентичен дизайну журнала в версии 5.0!

Для начала создадим Page, который будет аналогом сабформы 522 «Applied Item Entries Temp» в NAV 5.0. Ниже Вы можете увидеть структуру объекта и его свойства:

Page Designer

Далее. Сохраним Page 521 в виде нового объекта 50005 «Application Worksheet 2» (дабы не «портить» стандарт). Отличие структуры нового объекта будет заключаться в том, что мы добавим в него ряд новых элементов. Они выделены светло-зеленым цветом:

Новые элементы в странице

Элемент “Entry No” я пока что оставлю без комментариев, приведя лишь основные его свойства:

Свойства элемента Entry No

Метки Label1 и Label2 содержат текстовые константы Text001 и Text002 соответственно, позаимствованные из версии 5.0. Свойства элементов типа Part (Subpage1 и Subpage2) представлены ниже:

Subpage

Теперь приведу ряд картинок, на которых можно увидеть незначительные изменения кода (комментарии ///MyArticle3), сделанные в Page 50005 «Application Worksheet 2». Все эти изменения напрямую позаимствованы из формы 521 версии NAV 5.0. Поэтому все функции и определение переменных Вы можете увидеть прямо там, останавливаться подробно на них я не буду.

Итак, триггер OnOpenPage():

onOpenPage

Триггер OnFindRecord:

onFindRecord

Процедура UpdateFilterFields():

UpdateFilterFields

Процедура InitSubpages():

InitSubpages

Процедура ShowMenu:

ShowMenu

Теперь настало время объяснить, для чего же мы сделали integer-переменную EntryNo:

  1. для того чтобы «запоминать» номер текущей операции и, посредством этого, написать аналог OnAfterGetCurrRecord()-триггера формы 521 NAV 5.0;
  2. аналогом OnAfterGetCurrRecord()-триггера будет являться управление триггером EntryNo - OnControlAddIn(Index : Integer;Data : Text[1024]).

Итак, как уже было замечено, в процедуре UpdateFilterFields() происходит инициализация/изменение значения переменной EntryNo. Процедура UpdateFilterFields(), в свою очередь, запускается из ПРОЦЕДУРЫ OnAfterGetCurrRecord(). Процедура OnAfterGetCurrRecord() запускается из триггера OnAfterGetRecord(). (так уж реализовано в стандарте, мы просто позаимствовали подобную реализацию).

Итак, напишем простейший Add-in:

[ControlAddInExport("ApplicationWorksheetControl")] 
public class ApplicationWorksheetControl : StringControlAddInBase 
{ 
  protected override Control CreateControl() 
  { 
    TextBox myControl = new TextBox(); 
    myControl.TextChanged += new EventHandler(myControl_TextChanged); 
    return myControl; 
  } 
   void myControl_TextChanged(object sender, EventArgs e) 
  { 
    this.RaiseControlAddInEvent(1, "OnAfterGetCurrRecord"); 
  } 
   public override string Value 
  { 
    get 
    { 
      return base.Value; 
    } 
    set 
    { 
      base.Value = value; 
    } 
  } 
}

Когда значение, содержащееся в текстовом контроле, будет меняться, сработает функция myControl_TextChanged, посылающая в RTC соответствующее уведомление.

Напишем обработку контрола в NAV:

onControlAddin

Стоит сказать, что подобный код (без двух CASE’ов, естественно) в базовой ФУНКЦИИ OnAfterGetRecord данного Page’а приведет к вполне предсказуемой некорректной работе. Поэтому без написания и обработки Add-in’a здесь никак не обойтись…

Для полноты вынесем возможности применения/расприменения в Actions:

Action Designer

Функции обработки Action’ов были скопированы из Form 521 «Application Worksheet» NAV 5.0 и приведены ниже:

picture-17.png

Теперь посмотрим, что получилось в итоге:

Application Worksheet обновленная версия

Вроде бы все хорошо: есть главная форма с отображением товарных операций, есть операции применения на Subpage1, есть доступные к применению операции на Subpage2. Но, как оказалось, работать Actions данного Page не будут…

Дело в том, что:

  1. конструкция типа CurrPage.SETSELECTIONFILTER(Record) не работает с субпейджами;
  2. невозможно программно позиционироваться и определить текущую активную запись Subpage из главного Page.

К чему я это все. Взглянем на код функции GetRecords, вызываемой в Subpage по нажатию Action «Remove Application»:

GetRecords

При выборе 2 и более записей на Subpage, система все равно вернет ПЕРВУЮ запись данного Subpage. Таким образом, данный код работать не будет (по крайней мере, так, как мы от него ожидаем). И, как Вы догадались, даже при случае выбора только второй записи, система все равно вернет первую, на которой мы даже не позиционируемся.

Но решение есть. И заключается оно в том, что Actions необходимо выносить не в Page, а в Subpage, записи которого мы хотим обработать! В классическом клиенте, как Вы знаете, подобная логика работы на формах невозможна: все контролы типа Button, Menu Button, к примеру, находятся на главной форме.

Но есть одно «Но», которое возникает после переноса Actions на сабпейдж: после того, как мы перенесем обработку Actions непосредственно на сабпейджы, необходимо будет сделать так, чтобы сабпейджы физически были разными объектами системы. Иначе мы не сможем отличить операции расприменения/применения. Поскольку первая операция должна выполняться на Subpage1, а вторая – на Subpage2.

Таким образом, сделаем копию пейджа «Applied Item Entries Temp», создав новый пейдж 50007 «Applied Item Entries Temp 2». Ну и не забудем заодно заменить свойство контрола Subpage2 журнала применения: PagePartID – «Applied Item Entries Temp 2».

Итак, начнем обрабатывать Subpage1… Дабы запоминать номер «родительской операции», к которой применена операция сабпейджа, изменим функцию SetMyView следующим образом (к слову, скопируем эту функцию в новый пейдж 50007 «Applied Item Entries Temp 2»):

SetMyView

Теперь скопируем недавно созданный нами Action Remove Application с главного пейджа в Page «Applied Item Entries Temp». Вот его обработчик:

picture-21.png

Скопируем созданный нами Action Apply с главного пейджа в Page «Applied Item Entries Temp 2»:

ApplyRec

А вот, что в итоге получилось:

Журнал применения для ролеориентированного клиента - последняя версия

В Subpage1 «Applied Entries» вынесено расприменение, в Subpage2 «Entries Open for Application» вынесено применение (видно на предыдущем screenshot).

Большой недостаток представленного решения: невозможно скрыть поле «Entry No.» в главном Page. Если установим его свойство Visible = FALSE, то Add-in не будет работать (содержимое самого текстового поля меняться в этом случае не будет – событие не сработает).

Предлагайте Ваши улучшения, пожелания и критику к представленному функционалу. Искренне буду рад прочесть.

Метки: ,



Комментариев: 4

  1. Айрат пишет:

    Вчера столкнулся с подобной задачей. Большое спасибо, ваша статья очень помогла.
    “Большой недостаток представленного решения: невозможно скрыть поле «Entry No.» в главном Page. ”
    Предлагаю свои улучшения:
    1) Сделать аналогичную сборку-болванку, которая не изменяет внешний вид контролов. Можно взять полностью код из данной статьи.
    2) Нужно привязать ControlAddIn к любому уже из имеющихся фильтров (например, Item Filter).
    3) В триггере OnAfterGetRecord() напишем бесполезный код (!!! ВНИМАНИЕ, выполнение данного пункта обязательно, иначе Navision не будет вызывать событие OnAfterGetRecord при выборе пользователем записи) вроде “Item Filter”:=”Item Filter” (этот бесполезный код должен описывать присвоение именно той переменной, на которую повешен наш ControlAddIn).
    4) (Выполнение данного пункта тоже обязательно, хотя тут все пункты обязательны) В триггере “Item Filter - OnControlAddIn(Index : Integer;Data : Text[1024])” должен обязательно стоять непустой код, тут же достаточно написать комментарий. Например:

    Item Filter - OnControlAddIn(Index : Integer;Data : Text[1024])
    //some text

    Ну и все. Теперь при переходе пользователем по записям будет срабатывать триггер OnAfterGetRecord(), где можно прописать необходимый код фильтрации связанных таблиц на субформах.

  2. Айрат пишет:

    Извиняюсь, деинформировал.
    Все-таки, надо именно изменять значение переменной в триггере OnAfterGetRecord(). Иначе не будет работать. Поэтому, действительно, без «Entry No.» здесь не обойтись.

  3. Orwell пишет:

    Спасибо за комментарий, Айрат.
    Не было возможности проверить в NAV 2013. Но, предположу, что подобный технический недостаток там так же не обойти…

  4. Айрат пишет:

    Но, насколько мне известно, в NAV 2013 уже появился триггер OnAfterGetCurrRecord() на страницах. Поэтому можно обойтись им.
    Вернусь опять к NAV 2009. Если задача простая, и необоходимо только фильтровать данные связанной таблицы на субформе определенным образом, то можно вообще обойтись свойством субформы SubFormLink.
    У меня же задача стояла так, что данные на субформе либо фильтруются в зависимости от позиции на главной форме либо не фильтруются (фильтруются или нет зависит от галочки в области фильтров основной формы). Поэтому, обойтись свойством SubFormLink не вышло. Мне на странице нужен был именно аналог триггера OnAfterGetCurrRecord() формы. Рассматривался ещё вариант с видимостью форм/страниц, но страницы нельзя динамически отображать/скрывать. И, соответственно, остался только вариант с ControlAddIn.

Оставьте свой отзыв!