# Урок 3. Выпадающий список

Продолжим совершенствовать наш проект. Создадим форму для списка клиентов и карточку клиента. И заменим стартовую форму проекта. Теперь это будет форма списка клиентов.

На этом уроке:

* познакомимся со сниппетами, которые ускоряют разработку форм;
* рассмотрим применение [SetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/set_dc);
* рассмотрим возможность передавать значения параметров в момент вызова команды.

{% hint style="success" %}
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье [Разворачивание проекта](https://wfsys.gitbook.io/workflow-technology/setting-up-dev-environment/manual-deployment-project).

При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела [Ответы](https://wfsys.gitbook.io/wt-practice/lesson_table_editing#answer) прошлого урока. Скопируйте папки *Forms*, *Workflow* и *Patterns* в папку с развернутым проектом, например, в папку *D:\WT\Projects\Template\Projects\1. Template*.

Инструкция по подключению шаблонов находится по [ссылке](https://wfsys.gitbook.io/wt-practice/lesson_list_form#podklyuchenie-shablonov-k-proektu).
{% endhint %}

## Форма списка клиентов

Форма списка клиентов должна содержать таблицу и три кнопки редактирования списка. Сохранение данных в таблицу в базе данных будет происходить на форме карточки клиента. Поэтому на форме списка кнопка "Сохранить" и FootPanel будут отсутствовать.

### Создание формы

Для создания формы воспользуйтесь паттерном **Empty Form** (пустая форма с хэдером).

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FHpTPGfiGLI3sU98Wyi2g%2Fimage.png?alt=media&#x26;token=46a1fa20-1027-4c91-96c9-a7bd017c04c0" alt=""><figcaption></figcaption></figure>

После того как создали файл формы, необходимо подменить стартовую форму. В конфигурационном файле клиентской части (WorkflowForms.dll.config) в параметре **StartFormFileName** указан путь до стартовой формы. Его оставим без изменений. А переименуем сами xml-файлы форм. Для этого перейдем в папку форм проекта Template\Projects\1. Template\Forms\ и переименуем файлы:

* файл TemplateStart.xml переименуем в TemplateCityList.xml, т.к. это форма списка городов;
* а только что созданный TemplateClientList.xml переименуем в TemplateStart.xml, таким образом будущий список клиентов станет стартовой формой.

После этого перейдем в Eclipse и обновим состояние нашего проекта в окне Project Explorer, чтобы редактор увидел изменения в именах файлов. Для этого выберем наш проект и нажмем клавишу **F5**, либо кликнем правой кнопкой мыши на имени проекта и в открывшемся меню выберем пункт **Refresh**. Затем перейдем в файл формы списка городов и в тэге `<Form>` исправим значение атрибута **Name** на **TemplateCityListForm**. Это необходимо, чтобы в журнале событий ошибки имели правильное и уникальное имя формы, и мы могли быстро найти нужный файл.

Перезапустите проект и убедитесь, что стартовая форма успешно заменилась новым файлом.

Самостоятельно создайте необходимые элементы формы:&#x20;

* таблицу клиентов с колонками ClientId, № (RowNumber), ФИО (Name), Город (CityTitle) и Телефон (Phone);
* три кнопки редактирования списка. Не забудьте про условие активности кнопок, если выделена строка таблицы.

Пока нужен только интерфейс.

В результате у вас должна получиться похожая форма:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FZjcEyiNZJ2geJY5wEGe9%2Fimage.png?alt=media&#x26;token=14f6f9f3-7670-4fd0-94d6-0835a775488e" alt=""><figcaption></figcaption></figure>

### Загрузка данных из базы данных на форму

#### Создание таблицы client в базе данных

Перейдем в программу для управления СУБД PostgreSQL. И для нашей базы данных **template\_project** выполним следующий скрипт:

```sql
  CREATE SEQUENCE template.client_id_seq;
  
  CREATE TABLE template.client
  (
    client_id bigint NOT NULL DEFAULT nextval('template.client_id_seq'::regclass),
    city_id smallint,
    date_of_birth date,
    title character varying,
    email character varying,
    phone character varying,
    CONSTRAINT pk_client_id PRIMARY KEY (client_id),
    CONSTRAINT fk_city_id FOREIGN KEY (city_id)
      REFERENCES template.city (city_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
  );
```

{% hint style="info" %}
Обратите внимание, что для колонки **client\_id** используем тип **bigint**, а для **city\_id** используем тип **smallint**. Это связано с тем, что таблица **template.client** потенциально может иметь количество записей, которое превысит максимальное значение типа smallint, равное 32767. В то время, как для таблицы **template.city** тип **bigint** будет избыточным.
{% endhint %}

#### Описание запроса для чтения таблицы client базы данных

Перейдем в файл описания работы серверной части приложения (Template.xml). Добавим запрос для получения списка клиентов:

{% code title="Template.xml" %}

```markup
<SqlQuery Name="ClientSelectSqlQuery">
  <Text>
    SELECT
      client.client_id AS "ClientId",
      client.title AS "Name",
      city.title AS "CityTitle",
      client.phone AS "Phone"
    FROM
      template.client
      LEFT JOIN template.city USING(city_id)
    ORDER BY client.title;
  </Text>
</SqlQuery>
```

{% endcode %}

Создайте ClientViewSqlQueryPermission с этим запросом и дайте права доступа группе GuestGroup.

На форме списка клиентов (TemplateStart.xml) создайте [PrimaryGetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/primary_dc) на этот запрос и укажите его в тэге `<SourceDataConnection>`для таблицы **ClientDatabaseTable**.

Перезапустите проект. Убедитесь, что форма успешно загружена, и в журнале нет никаких сообщений об ошибках.

Отлично! Теперь можем заняться формой для карточки клиента.

## Карточка клиента

Карточка клиента должна содержать поля: ФИО, Дата рождения, Город, Контактный телефон и E-mail. Поле "Город" будет представлено в виде выпадающего списка. Справа от поля "Город" будет кнопка, по которой будет открываться существующая форма со списком городов.

Скачайте новый архив с шаблонами по ссылке ниже. Замените содержимое папки с шаблонами, на которую ссылаются свойства проекта, на содержимое из архива.

{% file src="<https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FvLnjsmid2aeEGsMB3uxH%2Flesson3-Patterns.zip?alt=media&token=73422c6a-d853-4987-bce5-022034ca7d2f>" %}
Шаблоны форм
{% endfile %}

С помощью шаблона **Entity Form** создайте форму для карточки клиента со следующими параметрами:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FZ6Lu32tOiOpQpucPJzKT%2Fimage.png?alt=media&#x26;token=bd23b282-b938-48ba-a13a-9a3734631037" alt=""><figcaption></figcaption></figure>

### Одна команда на открытие формы

Вернемся в файл стартовой формы (TemplateStart.xml). Создадим команду FormShowCommand  для формы карточки клиента, в которой сразу укажем два параметра:

{% code title="TemplateStart.xml" %}

```markup
<Command Name="ClientEditFormShowCommand" Type="FormShowCommand" Assembly="Commands">
  <Xml Type="Path">TemplateClientEdit.xml</Xml>
  <Show Type="None" />
  <Parameters>
    <Parameter Name="Edit"> </Parameter>
    <Parameter Name="ClientId"> </Parameter>
  </Parameters>
</Command>
```

{% endcode %}

Форма списка клиентов в таблице содержит колонки не для всех полей, которые описаны в задании на карточку клиента. Нет смысла перегружать sql-запрос для списка клиентов лишними полями,  которые понадобятся на другой форме. В карточке клиента будем использовать дополнительный sql-запрос, в котором для конкретного клиента будем получать все необходимые данные. Для этого нам и понадобится параметр **ClientId**. В файле карточки клиента (TemplateClientEdit.xml) эти параметры уже есть.

Также мы не будем делать отдельные команды открытия формы на добавление и редактирование. Нам хватит и одной. А значения параметров будем подставлять в месте вызова команды. В этом нам поможет универсальное значение [Input](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/values/input), которое используется только при описании команд и позволяет сократить дублирование кода.

Для параметра **Edit** в качестве значения укажем `<Input>` со значением по умолчанию **False**. А для параметра **ClientId** укажем `<Input>` без значения.

{% code title="TemplateStart.xml" %}

```markup
<Command Name="ClientEditFormShowCommand" Type="FormShowCommand" Assembly="Commands">
  <Xml Type="Path">TemplateClientEdit.xml</Xml>
  <Show Type="None" />
  <Parameters>
    <Parameter Name="Edit">
      <Input Name="Edit">False</Input>
    </Parameter>
    <Parameter Name="ClientId">
      <Input Name="ClientId" />
    </Parameter>
  </Parameters>
</Command>
```

{% endcode %}

Для открытия формы в режиме добавления записи нам не нужно передавать **ClientId**. А для параметра **Edit** мы уже указали значение по умолчанию. Таким образом, на кнопке **ClientAddButton** в тэге `<Commands>` пропишем простой вызов команды по имени:

```markup
<Commands>
  <Command Name="ClientEditFormShowCommand" />
</Commands>
```

Для открытия формы в режиме редактирования в команду **ClientEditFormShowCommand** через Input будем передавать нужные значения параметров. И так как открыть форму карточки клиента можно либо по кнопке "Редактировать запись...", либо по двойному клику по строке в таблице, мы создадим [SequentialCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/sequential_command), в которой будем вызывать **ClientEditFormShowCommand**:

{% code title="TemplateStart.xml" %}

```markup
<Command Name="ClientEditSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <Command Name="ClientEditFormShowCommand">
      <Input Name="Edit">True</Input>
      <Input Name="ClientId">
        <Object Name="ClientDatabaseTable">
          <Property Name="SelectedRowCellValueByColumnName">
            <Parameters>
              <Parameter Name="ColumnName">ClientId</Parameter>
            </Parameters>
          </Property>
        </Object>
      </Input>
    </Command>
  </Commands>
</Command>
```

{% endcode %}

Укажем команду **ClientEditSequentialCommand** в тэге `<Commands>` кнопки "Редактировать запись...".

Добавьте самостоятельно обработку двойного клика по строке таблицы и вызов команды **ClientEditSequentialCommand**.

Также самостоятельно создайте команду типа [DataConnectionRefreshCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/dc_refresh_command) на **ClientPrimaryGetDataConnection**, которая будет выполняться в Execution по изменению параметра **Updated** у команды **ClientEditFormShowCommand**.

Перезапустите проект. Убедитесь, что форма успешно загружена, и карточка клиента  открывается без сообщений об ошибках.

Отлично! Теперь можем приступить к наполнению формы карточки клиента объектами. Но для начала реализуем запрос и загружающее соединение с данными для получения информации по клиенту на форме.

### Запросы к базе данных

Перейдем в файл описания работы серверной части приложения (Template.xml). Добавим запрос для получения данных по конкретному клиенту:

{% code title="Template.xml" %}

```markup
<SqlQuery Name="ClientByIdSelectSqlQuery">
  <Text>
    SELECT
      client.title AS "Name",
      client.city_id AS "CityId",
      client.date_of_birth AS "DateOfBirth",
      client.email AS "Email",
      client.phone AS "Phone"
    FROM
      template.client
    WHERE
      client.client_id = {ClientId};
  </Text>
</SqlQuery>
```

{% endcode %}

Добавим его в ранее созданный **ClientViewSqlQueryPermission**.

Давайте сразу сделаем запросы на сохранение изменений в таблицу **template.client**:

{% code title="Template.xml" %}

```markup
<SqlQuery Name="ClientInsertSqlQuery">
  <Text>
    INSERT INTO template.client(
      city_id,
      date_of_birth,
      title,
      email,
      phone
    )
    VALUES (
      {CityId},
      {DateOfBirth},
      {Name},
      {Email},
      {Phone}
    )
    RETURNING client_id;
  </Text>
</SqlQuery>
    
<SqlQuery Name="ClientUpdateSqlQuery">
  <Text>
    UPDATE template.client
    SET
      city_id = {CityId},
      date_of_birth = {DateOfBirth},
      title = {Name},
      email = {Email},
      phone = {Phone}
    WHERE
      client_id = {ClientId};
  </Text>
</SqlQuery>
    
<SqlQuery Name="ClientDeleteSqlQuery">
  <Text>
    DELETE FROM template.client
    WHERE
      client_id = {ClientId};
  </Text>
</SqlQuery>
```

{% endcode %}

Запрос **ClientInsertSqlQuery** возвращает идентификатор **client\_id** новой записи. Этот идентификатор будем использовать на формах, как результат выполнения команды [SaveCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/save_command). Но об этом позже, а пока настроим права доступа к новым запросам.

Соберем новые запросы в **ClientEditSqlQueryPermission**:

{% code title="Template.xml" %}

```markup
<Permission Name="ClientEditSqlQueryPermission" Type="SqlQueryPermission">
  <SqlQueries>
    <SqlQuery Name="ClientInsertSqlQuery" />
    <SqlQuery Name="ClientUpdateSqlQuery" />
    <SqlQuery Name="ClientDeleteSqlQuery" />
  </SqlQueries>
</Permission>
```

{% endcode %}

И сделаем **ClientEditRole:**

{% code title="Template.xml" %}

```markup
<Role Name="ClientEditRole">
  <Permissions>
    <Permission Name="ClientViewSqlQueryPermission" />
    <Permission Name="ClientEditSqlQueryPermission" />
  </Permissions>
</Role>
```

{% endcode %}

Скорректируем права доступа для **GuestGroup**:

{% code title="Template.xml" %}

```markup
<Group Name="GuestGroup">
  <Roles>
    <Role Name="CityEditRole" />
    <Role Name="ClientEditRole" />
  </Roles>
</Group>
```

{% endcode %}

### Создание объектов формы

Перейдем в файл карточки клиента (TemplateClientEdit.xml) и первым делом создадим [PrimaryGetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/primary_dc):

{% code title="TemplateClientEdit.xml" %}

```markup
<DataConnection Name="ClientPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <SqlQuery Name="ClientByIdSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="Name" />
      <Field Name="CityId" />
      <Field Name="DateOfBirth" />
      <Field Name="Email" />
      <Field Name="Phone" />
    </Fields>
    <Parameters>
      <Parameter NativeName="ClientId">
        <Value>
          <Parameter Name="ClientId" />
        </Value>
      </Parameter>
    </Parameters>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

Создавать объекты на форме можно по одному: отдельно Label, отдельно связанный с ним TextBox. А можно воспользоваться сниппетом, который вставит пару объектов с корректной привязкой координат относительно друг друга и контейнера, в котором они располагаются.

Вставить сниппет можно из окна автозаполнения. Разместим курсор внутри **ContentPanel** и начнем писать "Lab". Нажмите на сочетание клавиш Ctrl+Space, чтобы открылось окно автозаполнения.

![](https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M_eBlWEU4C3o2GVEAAr%2F-MacShYCqlr4XRrseHGb%2F-MacVm_OckgieHaf-thH%2F3.5.%20%D0%98%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%BF%D0%BE%D0%B4%D1%81%D0%BA%D0%B0%D0%B7%D0%BA%D0%B8%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%BD%D0%B8%D0%BF%D0%BF%D0%B5%D1%82%D0%BE%D0%B2.png?alt=media\&token=8955acef-9594-452d-aa3b-556f1e671b14)

Редактор подсказывает нам три сниппета: "Label + ComboBox", "Label + DateTimePicker" и "Label + TextBox". Справа от имени сниппета указывается его короткое имя. Например, lcombox. По этому имени также можно искать конкретный сниппет. Эти сниппеты пригодятся нам при создании объектов на форме.

Выберем сниппет "Label + TextBox" и нажмем Enter. Откроется окно настроек:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FOyBad6fCltNKZgaasLwa%2Fimage.png?alt=media&#x26;token=c5147bd7-1a19-4e0a-aadc-3897c48dc473" alt=""><figcaption></figcaption></figure>

Последние два поля оставим пустыми, т.к. в **ContentPanel** у нас еще нет никаких объектов, к которым можно привязаться. Жмем Finish.

Редактор на место курсора подставит код наших объектов **NameLabel** и **NameTextBox**. Также откроет блокнот с текстом подсказки необходимых изменений. Сейчас эта подсказка нас не интересует - все необходимые доработки уже существуют.

Давайте смотреть, что добавил редактор. В объекте **NameLabel** редактор подсвечивает ошибки:&#x20;

![](https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M_eBlWEU4C3o2GVEAAr%2F-MacYVlWrkvcP58DalB2%2F-Macdb4d0Ep7drgDdYkj%2F3.7.%20%D0%9E%D1%88%D0%B8%D0%B1%D0%BA%D0%B8%20%D1%81%D0%BD%D0%B8%D0%BF%D0%BF%D0%B5%D1%82%D0%B0%20Label-TextBox.png?alt=media\&token=b51840df-716b-4f13-9807-ae1f8cb12fa9)

Это из-за того, что последние два поля в настройках сниппета мы оставили пустыми. Давайте заменим значения для тэгов `<Top>` и `<Left>` на **5** и **10** соответственно.

Вторым интересным моментом является то, что редактор для объекта **NameTextBox** в тэг `<Text>` сразу подставил наше соединение с данными **ClientPrimaryGetDataConnection** и указал нужное поле.

```markup
<Text>
  <DataConnection SourceDataConnection="ClientPrimaryGetDataConnection">
    <Fields>
      <Field Name="Name" />
    </Fields>
  </DataConnection>
</Text>
```

Перейдем в приложение и нажмем кнопку "Добавить запись...". Убедимся, что карточка клиента успешно загружена, и проверим расположение объектов.

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FABLI3GjiEPVlfv8flCa7%2Fimage.png?alt=media&#x26;token=9f0d14c3-4aa1-4499-85fd-7fc169e995ca" alt=""><figcaption></figcaption></figure>

Сначала доделаем все поля на форме, а затем скорректируем ее размеры.

Для создания поля "Дата рождения" воспользуемся сниппетом "Label + DateTimePicker":

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FNd384pWvZ0OdG8S1FCDL%2Fimage.png?alt=media&#x26;token=548fbc86-6630-4253-96ff-650bc91c51e8" alt=""><figcaption></figcaption></figure>

Теперь заполним два последних поля. И укажем в них ранее созданные поля.

Жмем Finish.

Обратите внимание на значения тэгов `<Top>` у **DateOfBirthLabel** и **DateOfBirthDateTimePicker**. Отступ между объектами **NameTextBox** и **DateOfBirthLabel** равен 5 пикселей. А отступ между **DateOfBirthLabel** и **DateOfBirthDateTimePicker** - нулевой. Такие значения отступов позволяют визуально отделить поля одного свойства от полей другого свойства.

Редактор для объекта **DateOfBirthDateTimePicker** в тэг `<Value>` сразу подставил наше соединение с данными **ClientPrimaryGetDataConnection** и указал нужное поле.

Перейдем в приложение и убедимся, что карточка клиента успешно загружена, и проверим расположение объектов.

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FxREC2xA2pQsfS2BWv3F6%2Fimage.png?alt=media&#x26;token=6e184100-4f74-45e0-b4c3-e87cc261e4b1" alt=""><figcaption></figcaption></figure>

Внесем небольшие правки в описание объекта **DateOfBirthDateTimePicker**. Во-первых, изменим его ширину, прописав в тэг `<Width>` значение **150**, чтобы поле выглядело симпатичнее. Во-вторых, изменим значение атрибута **Show** у тэга `<NullValue>` на **True**. Атрибут **Show** определяет, может ли дата иметь значение **NULL**.

В итоге синтаксис объекта **DateOfBirthDateTimePicker** будет выглядеть так:

{% code title="TemplateClientEdit.xml" %}

```markup
<MyObject Name="DateOfBirthDateTimePicker" Type="DateTimePicker" Assembly="BaseControls">
  <Top>
    <Object Name="DateOfBirthLabel">
      <Property Name="Bottom" />
    </Object>
  </Top>
  <Left>
    <Object Name="DateOfBirthLabel">
      <Property Name="Left" />
    </Object>
  </Left>
  <Width>150</Width>
  <Format>dd MMMM yyyy</Format>
  <ShowCalendar>True</ShowCalendar>
  <NullValue Show="True" />
  <Value>
    <DataConnection SourceDataConnection="ClientPrimaryGetDataConnection">
      <Fields>
        <Field Name="DateOfBirth" />
      </Fields>
    </DataConnection>
  </Value>
</MyObject>
```

{% endcode %}

По заданию поле "Город" должно быть сделано в виде выпадающего списка ([ComboBox](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/combobox)). Поэтому для его создания воспользуемся сниппетом "Label + ComboBox":

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FkhtJRg2kKBCwx7iVQx8a%2Fimage.png?alt=media&#x26;token=532fe504-808d-442e-bafd-b6a5d664652e" alt=""><figcaption></figcaption></figure>

Помимо знакомых уже полей настроек, этот сниппет имеет две специальных настройки: **Имя схемы БД** и **Имя таблицы БД**. Они необходимы для автоматического создания SqlQuery, на основе которого будет заполняться выпадающий список.

Жмем Finish.

Как можем видеть, редактор в файле описания работы серверной части приложения (Template.xml) создал запрос **CityShortSelectSqlQuery**. Осталось добавить этот запрос в какой-нибудь [SqlQueryPermission](https://wfsys.gitbook.io/workflow-engine-syntax/workflow_engine/permissions/sql_query_permission). Например, в **CityViewSqlQueryPermission**.

Вы могли заметить, что текст запроса в созданном **CityShortSelectSqlQuery** полностью совпадает с текстом запроса в **CitySelectSqlQuery**. Такое дублирование кода допустимо. Лучше иметь отдельный запрос на заполнение выпадающего списка. Забегая вперед, назову две причины такого разделения. Во-первых, в будущем это упростит настраивание прав доступа. А, во-вторых, текст обоих запросов будет дополняться и изменяться по-разному.

Вернемся в файл карточки клиента (TemplateClientEdit.xml) и посмотрим, что добавилось там.

Редактор создал **CityShortPrimaryGetDataConnection**, который прописал в тэг `<ValueList>` созданного объекта **CityComboBox**. А в тэг `<Value>` того же объекта подставил **ClientPrimaryGetDataConnection** и указал нужное поле.

Перейдем в приложение и убедимся, что карточка клиента успешно загружена, и проверим расположение объектов.

Самостоятельно создайте поля "Контактный телефон" и "E-mail", использую нужные сниппеты. И скорректируйте размеры формы.

В результате у вас должна получиться форма подобного вида:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2F6BQEwuhtPc5cCCaMwLKq%2Fimage.png?alt=media&#x26;token=6f7b38af-7a4e-45e4-b190-11f577c2b211" alt=""><figcaption></figcaption></figure>

### Редактирование выпадающего списка

По заданию справа от поля "Город" должна располагаться кнопка, по которой будет открываться форма со списком городов. Давайте создадим ее.

Для начала скачайте архив с изображением для этой кнопки и распакуйте его в папку **\Template\Projects\1. Template\Forms\Images\16x16**.

{% file src="<https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M_eBlWEU4C3o2GVEAAr%2F-MalmpmK-pwvw-imvfvq%2F-MalpLlmHKqdDpQvnmfj%2Flesson3-Images.zip?alt=media&token=6245fe48-9d99-417d-a1ca-53a0278666dd>" %}
Иконка для кнопки
{% endfile %}

Создайте самостоятельно команду на открытие формы TemplateCityList.xml.

Создадим саму кнопку, разместив код, представленный ниже, сразу после описания объекта **CityComboBox**.

{% code title="TemplateClientEdit.xml" %}

```markup
<MyObject Name="CityListButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Object Name="CityComboBox">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Formula>
      <Plus DataType="IntegerDataType">
        <Item>
          <Object Name="CityComboBox">
            <Property Name="Right" />
          </Object>
        </Item>
        <Item>5</Item>
      </Plus>
    </Formula>
  </Left>
  <Height>22</Height>
  <Width>22</Width>
  <Hint>Список городов...</Hint>
  <BackgroundImage>Images\16x16\list.png</BackgroundImage>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>1</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <Commands>
    <Command Name="CityListFormShowCommand" />
  </Commands>
</MyObject>
```

{% endcode %}

Не забудьте поправить ширину **CityComboBox** с учетом ширины созданной кнопки и отступа в 5 пикселей между объектами.

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

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2Fn4UHVuFqXP8hR2MCEFbl%2Fimage.png?alt=media&#x26;token=0e23a245-19f3-4020-985a-dad3df03fcb6" alt=""><figcaption></figcaption></figure>

Осталось реализовать обновление **CityShortPrimaryGetDataConnection**, который предоставляет список для **CityComboBox**, если на форме были сохранены изменения.

Для этого самостоятельно выполните следующие пункты:

* Создайте DataConnectionRefreshCommand на CityShortPrimaryGetDataConnection;
* Создайте условие для проверки параметра Updated у команды открытия формы TemplateCityList.xml;
* Создайте Execution, который по условию из предыдущего пункта будет вызывать команду на обновление CityShortPrimaryGetDataConnection;
* На форме TemplateCityList.xml создайте параметр Updated и команду UpdatedTrueValueSetCommand;
* Там же на форме TemplateCityList.xml в тэге `<Commands>` кнопки SaveButton исправьте список выполняемых команд. Удалите вызов команды CityDataConnectionRefreshCommand. Вместо нее добавьте вызовы команд UpdatedTrueValueSetCommand и FormCloseCommand;
* Так же на форме TemplateCityList.xml удалите описание команды CityDataConnectionRefreshCommand. Эта команда нам больше не понадобится.

Перейдем в приложение. Убедимся, что формы успешно загружаются, а при сохранении изменений на форме списка городов, в карточке клиента обновляется список городов в **CityComboBox**.

### Сохранение изменений в таблицу в базе данных

Запросы **ClientInsertSqlQuery**, **ClientUpdateSqlQuery** у нас уже есть. Давайте сделаем для них два  [SetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/set_dc):

{% code title="TemplateClientEdit.xml" %}

```markup
<DataConnection Name="ClientInsertSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
  <Workflow Name="Template" />
  <Parameters>
    <Parameter NativeName="Name">
      <Value>
        <Object Name="NameTextBox" />
      </Value>
    </Parameter>
    <Parameter NativeName="DateOfBirth">
      <Value>
        <Object Name="DateOfBirthDateTimePicker"/>
      </Value>
    </Parameter>
    <Parameter NativeName="CityId">
      <Value>
        <Object Name="CityComboBox" />
      </Value>
    </Parameter>
    <Parameter NativeName="Phone">
      <Value>
        <Object Name="PhoneTextBox" />
      </Value>
    </Parameter>
    <Parameter NativeName="Email">
      <Value>
        <Object Name="EmailTextBox" />
      </Value>
    </Parameter>
  </Parameters>
  <SqlQueries>
    <SqlQuery Name="ClientInsertSqlQuery" Type="Insert" />
  </SqlQueries>
</DataConnection>

<DataConnection Name="ClientUpdateSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
  <Workflow Name="Template" />
  <Parameters>
    <Parameter NativeName="ClientId">
      <Value>
        <Parameter Name="ClientId" />
      </Value>
    </Parameter>
    <Parameter NativeName="Name">
      <Value>
        <Object Name="NameTextBox" />
      </Value>
    </Parameter>
    <Parameter NativeName="DateOfBirth">
      <Value>
        <Object Name="DateOfBirthDateTimePicker"/>
      </Value>
    </Parameter>
    <Parameter NativeName="CityId">
      <Value>
        <Object Name="CityComboBox" />
      </Value>
    </Parameter>
    <Parameter NativeName="Phone">
      <Value>
        <Object Name="PhoneTextBox" />
      </Value>
    </Parameter>
    <Parameter NativeName="Email">
      <Value>
        <Object Name="EmailTextBox" />
      </Value>
    </Parameter>
  </Parameters>
  <SqlQueries>
    <SqlQuery Name="ClientUpdateSqlQuery" Type="Update" />
  </SqlQueries>
</DataConnection>
```

{% endcode %}

Как вы помните, чтобы SetDataConnection выполнился, нужна команда [SaveCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/save_command), которая будет активировать соединение с данными для отправки. Давайте создадим две команды для каждого SetDataConnection:

{% code title="TemplateClientEdit.xml" %}

```markup
<Command Name="ClientInsertSaveCommand" Type="SaveCommand" Assembly="Commands">
  <DataConnections>
    <DataConnection Name="ClientInsertSetDataConnection" />
  </DataConnections>
</Command>

<Command Name="ClientUpdateSaveCommand" Type="SaveCommand" Assembly="Commands">
  <DataConnections>
    <DataConnection Name="ClientUpdateSetDataConnection" />
  </DataConnections>
</Command>
```

{% endcode %}

Как вы помните, наш запрос **ClientInsertSqlQuery** возвращает идентификатор **client\_id** новой записи. Команда **ClientInsertSaveCommand** получает этот идентификатор от **ClientInsertSetDataConnection** и записывает его в свой результат выполнения. Давайте создадим команду типа [ValueSetCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/value_set_command), которая запишет этот результат в параметр формы **ClientId**:

{% code title="TemplateClientEdit.xml" %}

```markup
<Command Name="ClientIdValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Parameter Name="ClientId">
    <Command Name="ClientInsertSaveCommand" />
  </Parameter>
</Command>
```

{% endcode %}

Это нам нужно для того, чтобы на родительской форме мы могли использовать идентификатор нового клиента.

Скорректируем синтаксис команды **SaveSequentialCommand**, которая уже есть в коде:

{% code title="TemplateClientEdit.xml" %}

```markup
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <If>
      <When>
        <Parameter Name="Edit" />
      </When>
      <Then>
        <Command Name="ClientUpdateSaveCommand" />
      </Then>
      <Else>
        <Command Name="ClientInsertSaveCommand" />
        <Command Name="ClientIdValueSetCommand" />
      </Else>
    </If>
    <Command Name="UpdatedTrueValueSetCommand" />
    <Command Name="FormCloseCommand" />
  </Commands>
</Command>
```

{% endcode %}

Вернемся в файл стартовой формы (TemplateStart.xml). Здесь мы получим значение параметра **ClientId** из команды **ClientEditFormShowCommand**, которое будем использовать для выделение строки в таблице **ClientDatabaseTable**. Это позволит пользователю убедиться в том, что новая запись успешно сохранилась. Для выделения строки в таблице создадим следующую команду:

{% code title="TemplateStart.xml" %}

```markup
<Command Name="ClientSelectInTableValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="ClientDatabaseTable">
    <Property Name="SelectRowByFieldValue">
      <Parameters>
        <Parameter Name="ColumnName">ClientId</Parameter>
        <Parameter Name="Value">
          <Command Name="ClientEditFormShowCommand" Parameter="ClientId" />
        </Parameter>
      </Parameters>
    </Property>
  </Object>
</Command>
```

{% endcode %}

Добавьте вызов этой команды в Execution по изменению параметра **Updated** у команды **ClientEditFormShowCommand** после вызова команды типа [DataConnectionRefreshCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/dc_refresh_command).

Перезапустите проект. Убедитесь, что формы успешно загружаются, и происходит выделение строки нового клиента.

## Удаление записей

Вернемся в файл стартовой формы (TemplateStart.xml) и реализуем функционал удаления записей из таблицы клиентов.

У нас уже есть sql-запрос на удаление записи в таблице **template.client** в базе данных. Давайте создадим SetDataConnection для этого запроса:

{% code title="TemplateStart.xml" %}

```markup
<DataConnection Name="ClientDeleteSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
  <Workflow Name="Template" />
  <Parameters>
    <Parameter NativeName="ClientId">
      <Value>
        <Object Name="ClientDatabaseTable">
          <Property Name="SelectedRowCellValueByColumnName">
            <Parameters>
              <Parameter Name="ColumnName">ClientId</Parameter>
            </Parameters>
          </Property>
        </Object>
      </Value>
    </Parameter>
  </Parameters>
  <SqlQueries>
    <SqlQuery Name="ClientDeleteSqlQuery" Type="Delete" />
  </SqlQueries>
</DataConnection>
```

{% endcode %}

Sql-запрос ожидает только один параметр **ClientId**. В качестве значения в этот параметр передадим значение из одноименной колонки выделенной строки таблицы **ClientDatabaseTable**.

После удаления записи из таблицы в базе данных нам нужно будет обновить данные, получаемые из **ClientPrimaryGetDataConnection**. Это можно сделать используя знакомую нам команду DataConnectionRefreshCommand. А можно использовать более компактный способ обновить DataConnection.

У [SetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/set_dc) и [DatabaseTableSetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/database_table_set_dc) есть необязательный тэг `<Refresh>`, в качестве значения которого указывается список тэгов `<DataConnection>` c именами загружающих соединений с данными, которые будут обновлены после выполнения сохранения.

Давайте добавим такой тэг в наш **ClientDeleteSetDataConnection**. Таким образом, общий синтаксис соединения с данными для отправки будет выглядеть так:

{% code title="TemplateStart.xml" %}

```markup
<DataConnection Name="ClientDeleteSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
  <Workflow Name="Template" />
  <Parameters>
    <Parameter NativeName="ClientId">
      <Value>
        <Object Name="ClientDatabaseTable">
          <Property Name="SelectedRowCellValueByColumnName">
            <Parameters>
              <Parameter Name="ColumnName">ClientId</Parameter>
            </Parameters>
          </Property>
        </Object>
      </Value>
    </Parameter>
  </Parameters>
  <SqlQueries>
    <SqlQuery Name="ClientDeleteSqlQuery" Type="Delete" />
  </SqlQueries>
  <Refresh>
    <DataConnection Name="ClientPrimaryGetDataConnection" />
  </Refresh>
</DataConnection>
```

{% endcode %}

Самостоятельно создайте команду SaveCommand для нового DataConnection и пропишите ее в тэге `<Commands>` кнопки "Удалить запись...".

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

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FtWuOSSxk6e08X7PIaWm1%2Fimage.png?alt=media&#x26;token=2a318143-db4c-4941-8963-3619e28f1781" alt=""><figcaption></figcaption></figure>

## Итоги <a href="#results" id="results"></a>

На уроке мы узнали, что помимо паттернов форм, есть сниппеты, которые позволяют вставлять куски кода в редактируемый файл. Сниппеты ускоряют процесс наполнения формы объектами.

Также рассмотрели использование универсального значения [Input](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/values/input), которое позволяет передавать в команду значения параметров в месте вызова команды, а не при ее описании.

Еще с первого урока у нас есть проблема с отсутствием проверки корректности введенных пользователем данных. На следующем уроке рассмотрим, как это сделать корректно и удобно для пользователя. Также рассмотрим паттерн onClose, суть которого заключается в проверке наличия изменений на форме и уведомлении пользователя о них при попытке закрыть форму без сохранения.

## Ответы <a href="#answer" id="answer"></a>

В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.

Ниже представлен архив с xml-файлами форм и серверным xml-файл, а также бэкап базы данных. С помощью файлов можете проверить себя.

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>lesson3-answer.zip</td><td><a href="https://wfsys.ru/download/wt_practice_desktop_answers/lesson3-answer.zip">https://wfsys.ru/download/wt_practice_desktop_answers/lesson3-answer.zip</a></td></tr></tbody></table>

s

{% file src="broken-reference" %}
