# Урок 14. Постраничный просмотр

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

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

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

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

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

## Постраничный просмотр <a href="#pagination" id="pagination"></a>

Рассмотрим паттерн Pagination на примере формы списка клиентов.

<figure><img src="/files/Z7TbppklFqS2UD0NsWKx" alt=""><figcaption></figcaption></figure>

### Подготовка формы <a href="#preparing-form" id="preparing-form"></a>

Перейдем в файл списка клиентов (TemplateClientList.xml) и в ContentPanel добавим описание панели PagePanel с объектами, необходимыми для постраничного просмотра списка клиентов:

<details>

<summary>PagePanel</summary>

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

```xml
<MyObject Name="PagePanel" Type="Panel" Assembly="BaseControls">
  <Top>
    <Object Name="ClientDatabaseTable">
      <Property Name="Bottom" />
    </Object>
  </Top>
  <Left>
    <Calculate>
      <Expression>Ceiling(({0} - {1})/2)</Expression>
      <Items>
        <Item>
          <Object Name="ContentPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="PagePanel">
            <Property Name="Width" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Height>30</Height>
  <Width>
    <Object Name="TotalItemsLabel">
      <Property Name="Right" />
    </Object>
  </Width>

  <MyObject Name="FirstPageButton" Type="Button" Assembly="BaseControls">
    <Top>5</Top>
    <Left>0</Left>
    <Height>
      <Object Name="CurrentPageTextBox">
        <Property Name="Height" />
      </Object>
    </Height>
    <Width>
      <Object Name="FirstPageButton">
        <Property Name="Height" />
      </Object>
    </Width>
    <FlatStyle>Flat</FlatStyle>
    <FlatBorderSize>0</FlatBorderSize>
    <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
    <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
    <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
    <BackgroundImageLayout>Center</BackgroundImageLayout>
    <BackgroundImage>Images\16x16\arrow-collapse-left.png</BackgroundImage>
    <Commands> </Commands>
  </MyObject>

  <MyObject Name="PrevPageButton" Type="Button" Assembly="BaseControls">
    <Top>
      <Object Name="FirstPageButton">
        <Property Name="Top" />
      </Object>
    </Top>
    <Left>
      <Calculate>
        <Expression>{0} + 3</Expression>
        <Items>
          <Item>
            <Object Name="FirstPageButton">
              <Property Name="Right" />
            </Object>
          </Item>
        </Items>
      </Calculate>
    </Left>
    <Height>
      <Object Name="FirstPageButton">
        <Property Name="Height" />
      </Object>
    </Height>
    <Width>
      <Object Name="FirstPageButton">
        <Property Name="Width" />
      </Object>
    </Width>
    <FlatStyle>Flat</FlatStyle>
    <FlatBorderSize>0</FlatBorderSize>
    <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
    <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
    <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
    <BackgroundImageLayout>Center</BackgroundImageLayout>
    <BackgroundImage>Images\16x16\arrow-left.png</BackgroundImage>
    <Commands> </Commands>
  </MyObject>

  <MyObject Name="CurrentPageTextBox" Type="TextBox" Assembly="BaseControls" ChangeForm="False">
    <Top>
      <Object Name="PrevPageButton">
        <Property Name="Top" />
      </Object>
    </Top>
    <Left>
      <Calculate>
        <Expression>{0} + 3</Expression>
        <Items>
          <Item>
            <Object Name="PrevPageButton">
              <Property Name="Right" />
            </Object>
          </Item>
        </Items>
      </Calculate>
    </Left>
    <Width>30</Width>
    <MaxLength>3</MaxLength>
    <AllowedSymbols>0123456789</AllowedSymbols>
    <TextAlign>Right</TextAlign>
    <Text> </Text>
  </MyObject>

  <MyObject Name="TotalPagesLabel" Type="Label" Assembly="BaseControls" ChangeForm="False">
    <Top>
      <Object Name="PrevPageButton">
        <Property Name="Top" />
      </Object>
    </Top>
    <Left>
      <Object Name="CurrentPageTextBox">
        <Property Name="Right" />
      </Object>
    </Left>
    <Height>20</Height>
    <Width>40</Width>
    <TextAlign>MiddleLeft</TextAlign>
    <Text> </Text>
  </MyObject>

  <MyObject Name="NextPageButton" Type="Button" Assembly="BaseControls">
    <Top>
      <Object Name="FirstPageButton">
        <Property Name="Top" />
      </Object>
    </Top>
    <Left>
      <Calculate>
        <Expression>{0} + 3</Expression>
        <Items>
          <Item>
            <Object Name="TotalPagesLabel">
              <Property Name="Right" />
            </Object>
          </Item>
        </Items>
      </Calculate>
    </Left>
    <Height>
      <Object Name="FirstPageButton">
        <Property Name="Height" />
      </Object>
    </Height>
    <Width>
      <Object Name="FirstPageButton">
        <Property Name="Width" />
      </Object>
    </Width>
    <FlatStyle>Flat</FlatStyle>
    <FlatBorderSize>0</FlatBorderSize>
    <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
    <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
    <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
    <BackgroundImageLayout>Center</BackgroundImageLayout>
    <BackgroundImage>Images\16x16\arrow-right.png</BackgroundImage>
    <Commands> </Commands>
  </MyObject>

  <MyObject Name="LastPageButton" Type="Button" Assembly="BaseControls">
    <Top>
      <Object Name="PrevPageButton">
        <Property Name="Top" />
      </Object>
    </Top>
    <Left>
      <Calculate>
        <Expression>{0} + 3</Expression>
        <Items>
          <Item>
            <Object Name="NextPageButton">
              <Property Name="Right" />
            </Object>
          </Item>
        </Items>
      </Calculate>
    </Left>
    <Height>
      <Object Name="FirstPageButton">
        <Property Name="Height" />
      </Object>
    </Height>
    <Width>
      <Object Name="FirstPageButton">
        <Property Name="Width" />
      </Object>
    </Width>
    <FlatStyle>Flat</FlatStyle>
    <FlatBorderSize>0</FlatBorderSize>
    <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
    <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
    <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
    <BackgroundImageLayout>Center</BackgroundImageLayout>
    <BackgroundImage>Images\16x16\arrow-collapse-right.png</BackgroundImage>
    <Commands> </Commands>
  </MyObject>

  <MyObject Name="PerPageLabel" Type="Label" Assembly="BaseControls">
    <Top>
      <Object Name="PrevPageButton">
        <Property Name="Top" />
      </Object>
    </Top>
    <Left>
      <Calculate>
        <Expression>{0} + 20</Expression>
        <Items>
          <Item>
            <Object Name="LastPageButton">
              <Property Name="Right" />
            </Object>
          </Item>
        </Items>
      </Calculate>
    </Left>
    <Height>20</Height>
    <Width>110</Width>
    <TextAlign>MiddleRight</TextAlign>
    <Text>строк на странице</Text>
  </MyObject>

  <MyObject Name="ItemsPerPageComboBox" Type="ComboBox" Assembly="BaseControls" ChangeForm="False">
    <Top>
      <Object Name="PrevPageButton">
        <Property Name="Top" />
      </Object>
    </Top>
    <Left>
      <Object Name="PerPageLabel">
        <Property Name="Right" />
      </Object>
    </Left>
    <Width>50</Width>
    <ValueList>
      <Structure Type="Table">
        <Row>
          <Item />
          <Item>[Все]</Item>
        </Row>
        <Row>
          <Item>5</Item>
          <Item>5</Item>
        </Row>
        <Row>
          <Item>10</Item>
          <Item>10</Item>
        </Row>
        <Row>
          <Item>50</Item>
          <Item>50</Item>
        </Row>
        <Row>
          <Item>100</Item>
          <Item>100</Item>
        </Row>
      </Structure>
    </ValueList>
    <Value>50</Value>
  </MyObject>

  <MyObject Name="TotalItemsLabel" Type="Label" Assembly="BaseControls" ChangeForm="False">
    <Top>
      <Object Name="PrevPageButton">
        <Property Name="Top" />
      </Object>
    </Top>
    <Left>
      <Calculate>
        <Expression>{0} + 3</Expression>
        <Items>
          <Item>
            <Object Name="ItemsPerPageComboBox">
              <Property Name="Right" />
            </Object>
          </Item>
        </Items>
      </Calculate>
    </Left>
    <Height>20</Height>
    <Width>60</Width>
    <TextAlign>MiddleLeft</TextAlign>
    <Text> </Text>
  </MyObject>

</MyObject>
```

{% endcode %}

</details>

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

{% file src="/files/f7iI5GFtOFxYCosQnYb7" %}
Иконки для кнопок
{% endfile %}

### Количество записей в ответе от сервера <a href="#number-of-records-in-response" id="number-of-records-in-response"></a>

Первым делом в запрос **ClientSelectSqlQuery** для списка клиентов добавим поле **Count**, в котором будем возвращать общее количество записей в результате запроса:

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

```xml
<SqlQuery Name="ClientSelectSqlQuery">
  <Text>
    SELECT
      client.client_id AS "ClientId",
      client.title AS "Name",
      city.title AS "CityTitle",
      client.phone AS "Phone",
      client.archive AS "Archive",
      COUNT(*) OVER() AS "Count"
    FROM
      template.client
      LEFT JOIN template.city USING(city_id)
    WHERE
      {SearchKeywords} ISNULL OR client.title ilike '%' || {SearchKeywords} ||'%'
    ORDER BY client.title;
  </Text>
</SqlQuery>
```

{% endcode %}

Создадим условие для проверки количества строк в **ClientPrimaryGetDataConnection**, используя для этого get-проперти [Count](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/primary_dc#get_count):

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

```xml
<Condition Name="NumberOfClientsEqual0Condition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <DataConnection SourceDataConnection="ClientPrimaryGetDataConnection">
        <Property Name="Count" />
      </DataConnection>
    </Item>
    <Item>0</Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>
```

{% endcode %}

Создадим переменную для хранения общего количества записей клиентов в базе данных:

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

```xml
<MyObject Name="NumberOfClientsVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>
    <Switch>
      <Case>
        <When>
          <Condition Name="NumberOfClientsEqual0Condition" />
        </When>
        <Then>0</Then>
      </Case>
      <Case>
        <DataConnection SourceDataConnection="ClientPrimaryGetDataConnection">
          <Fields>
            <Field Name="Count" />
          </Fields>
        </DataConnection>
      </Case>
    </Switch>
  </Value>
</MyObject>
```

{% endcode %}

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

Создадим условие проверки этой переменной, с помощью которого будем блокировать кнопки переключения по страницам:

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

```xml
<Condition Name="NumberOfClientsVariableEqual0Condition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="NumberOfClientsVariable" />
    </Item>
    <Item>0</Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>
```

{% endcode %}

Для объекта TotalItemsLabel скорректируем значение тэга `<Text>`, указав количество строк которые вернул сервер:

<details>

<summary>TotalItemsLabel</summary>

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

```xml
<MyObject Name="TotalItemsLabel" Type="Label" Assembly="BaseControls" ChangeForm="False">
  <Top>
    <Object Name="PrevPageButton">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Calculate>
      <Expression>{0} + 3</Expression>
      <Items>
        <Item>
          <Object Name="ItemsPerPageComboBox">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Height>20</Height>
  <Width>80</Width>
  <TextAlign>MiddleLeft</TextAlign>
  <Text>
    <String>
      <Format>из {0}</Format>
      <Items>
        <Item>
          <Object Name="NumberOfClientsVariable" />
        </Item>
      </Items>
    </String>
  </Text>
</MyObject>
```

{% endcode %}

</details>

### Переход между страницами <a href="#jump-between-pages" id="jump-between-pages"></a>

Создадим переменную для хранения номера текущей страницы:

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

```xml
<MyObject Name="CurrentPageVariable" Type="Variable" Assembly="SimpleControls">
  <Value>1</Value>
</MyObject>
```

{% endcode %}

Укажем эту переменную в тэге `<Text>` объекта CurrentPageTextBox.

<details>

<summary>CurrentPageTextBox</summary>

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

```xml
<MyObject Name="CurrentPageTextBox" Type="TextBox" Assembly="BaseControls" ChangeForm="False">
  <Top>
    <Object Name="PrevPageButton">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Calculate>
      <Expression>{0} + 3</Expression>
      <Items>
        <Item>
          <Object Name="PrevPageButton">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Width>30</Width>
  <MaxLength>3</MaxLength>
  <AllowedSymbols>0123456789</AllowedSymbols>
  <TextAlign>Right</TextAlign>
  <Text>
    <Object Name="CurrentPageVariable" />
  </Text>
</MyObject>
```

{% endcode %}

</details>

Создадим переменную для расчета количества страниц:

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

```xml
<MyObject Name="TotalPagesVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>
    <Switch>
      <Case>
        <When>
          <Condition Name="ItemsPerPageComboBoxIsNullCondition" />
        </When>
        <Then>1</Then>
      </Case>
      <Case>
        <Calculate>
          <Expression>Ceiling({0}/{1})</Expression>
          <Items>
            <Item>
              <Object Name="NumberOfClientsVariable" />
            </Item>
            <Item>
              <Object Name="ItemsPerPageComboBox" />
            </Item>
          </Items>
        </Calculate>
      </Case>
    </Switch>
  </Value>
</MyObject>
```

{% endcode %}

Где условие **ItemsPerPageComboBoxIsNullCondition** имеет вид:

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

```xml
<Condition Name="ItemsPerPageComboBoxIsNullCondition" Type="IsNullCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="ItemsPerPageComboBox" />
    </Item>
  </Items>
</Condition>
```

{% endcode %}

Укажем переменную **TotalPagesVariable** в тэге `<Text>` объекта TotalPagesLabel.

<details>

<summary>TotalPagesLabel</summary>

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

```xml
<MyObject Name="TotalPagesLabel" Type="Label" Assembly="BaseControls" ChangeForm="False">
  <Top>
    <Object Name="PrevPageButton">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Object Name="CurrentPageTextBox">
      <Property Name="Right" />
    </Object>
  </Left>
  <Height>20</Height>
  <Width>40</Width>
  <TextAlign>MiddleLeft</TextAlign>
  <Text>
    <String>
      <Format>/ {0}</Format>
      <Items>
        <Item>
          <Switch>
            <Case>
              <When>
                <Condition Name="NumberOfClientsVariableEqual0Condition" />
              </When>
              <Then>1</Then>
            </Case>
            <Case>
              <Object Name="TotalPagesVariable" />
            </Case>
          </Switch>
        </Item>
      </Items>
    </String>
  </Text>
</MyObject>
```

{% endcode %}

</details>

Команда изменения номера текущей страницы на произвольное значение:

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

```xml
<Command Name="CurrentPageVariableValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="CurrentPageVariable">
    <Input />
  </Object>
</Command>
```

{% endcode %}

Команда для перехода на предыдущую страницу:

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

```xml
<Command Name="CurrentPageVariableDecValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="CurrentPageVariable">
    <Formula>
      <Minus DataType="IntegerDataType">
        <Item>
          <Object Name="CurrentPageVariable" />
        </Item>
        <Item>1</Item>
      </Minus>
    </Formula>
  </Object>
</Command>
```

{% endcode %}

Команда для перехода на следующую страницу:

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

```xml
<Command Name="CurrentPageVariableIncValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="CurrentPageVariable">
    <Formula>
      <Plus DataType="IntegerDataType">
        <Item>
          <Object Name="CurrentPageVariable" />
        </Item>
        <Item>1</Item>
      </Plus>
    </Formula>
  </Object>
</Command>
```

{% endcode %}

Условие проверки, является ли текущая страница первой из доступных:

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

```xml
<Condition Name="CurrentPageVariableEqual1Condition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="CurrentPageVariable" />
    </Item>
    <Item>1</Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>
```

{% endcode %}

Условие проверки, является ли текущая страница последней из доступных:

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

```xml
<Condition Name="CurrentPageVariableEqualMaxCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="CurrentPageVariable" />
    </Item>
    <Item>
      <Object Name="TotalPagesVariable" />
    </Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>
```

{% endcode %}

Теперь можем скорректировать описание кнопок перехода по страницам:

<details>

<summary>FirstPageButton</summary>

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

```xml
<MyObject Name="FirstPageButton" Type="Button" Assembly="BaseControls">
  <Top>5</Top>
  <Left>0</Left>
  <Height>
    <Object Name="CurrentPageTextBox">
      <Property Name="Height" />
    </Object>
  </Height>
  <Width>
    <Object Name="FirstPageButton">
      <Property Name="Height" />
    </Object>
  </Width>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>0</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <BackgroundImage>Images\16x16\arrow-collapse-left.png</BackgroundImage>
  <Commands>
    <Command Name="CurrentPageVariableValueSetCommand">1</Command>
  </Commands>
  <Enabled>
    <And>
      <Not>
        <Condition Name="CurrentPageVariableEqual1Condition" />
      </Not>
      <Not>
        <Condition Name="NumberOfClientsVariableEqual0Condition" />
      </Not>
    </And>
  </Enabled>
</MyObject>
```

{% endcode %}

</details>

<details>

<summary>PrevPageButton</summary>

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

```xml
<MyObject Name="PrevPageButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Object Name="FirstPageButton">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Calculate>
      <Expression>{0} + 3</Expression>
      <Items>
        <Item>
          <Object Name="FirstPageButton">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Height>
    <Object Name="FirstPageButton">
      <Property Name="Height" />
    </Object>
  </Height>
  <Width>
    <Object Name="FirstPageButton">
      <Property Name="Width" />
    </Object>
  </Width>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>0</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <BackgroundImage>Images\16x16\arrow-left.png</BackgroundImage>
  <Commands>
    <Command Name="CurrentPageVariableDecValueSetCommand" />
  </Commands>
  <Enabled>
    <And>
      <Not>
        <Condition Name="CurrentPageVariableEqual1Condition" />
      </Not>
      <Not>
        <Condition Name="NumberOfClientsVariableEqual0Condition" />
      </Not>
    </And>
  </Enabled>
</MyObject>
```

{% endcode %}

</details>

<details>

<summary>NextPageButton</summary>

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

```xml
<MyObject Name="NextPageButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Object Name="FirstPageButton">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Calculate>
      <Expression>{0} + 3</Expression>
      <Items>
        <Item>
          <Object Name="TotalPagesLabel">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Height>
    <Object Name="FirstPageButton">
      <Property Name="Height" />
    </Object>
  </Height>
  <Width>
    <Object Name="FirstPageButton">
      <Property Name="Width" />
    </Object>
  </Width>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>0</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <BackgroundImage>Images\16x16\arrow-right.png</BackgroundImage>
  <Commands>
    <Command Name="CurrentPageVariableIncValueSetCommand" />
  </Commands>
  <Enabled>
    <And>
      <Not>
        <Condition Name="CurrentPageVariableEqualMaxCondition" />
      </Not>
      <Not>
        <Condition Name="NumberOfClientsVariableEqual0Condition" />
      </Not>
    </And>
  </Enabled>
</MyObject>
```

{% endcode %}

</details>

<details>

<summary>LastPageButton</summary>

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

```xml
<MyObject Name="LastPageButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Object Name="PrevPageButton">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Calculate>
      <Expression>{0} + 3</Expression>
      <Items>
        <Item>
          <Object Name="NextPageButton">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Height>
    <Object Name="FirstPageButton">
      <Property Name="Height" />
    </Object>
  </Height>
  <Width>
    <Object Name="FirstPageButton">
      <Property Name="Width" />
    </Object>
  </Width>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>0</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <BackgroundImage>Images\16x16\arrow-collapse-right.png</BackgroundImage>
  <Commands>
    <Command Name="CurrentPageVariableValueSetCommand">
      <Object Name="TotalPagesVariable" />
    </Command>
  </Commands>
  <Enabled>
    <And>
      <Not>
        <Condition Name="CurrentPageVariableEqualMaxCondition" />
      </Not>
      <Not>
        <Condition Name="NumberOfClientsVariableEqual0Condition" />
      </Not>
    </And>
  </Enabled>
</MyObject>
```

{% endcode %}

</details>

Но переход между страницами возможен и при ручном вводе значения в поле CurrentPageTextBox. Для этого создадим условие для проверки события нажатия клавиши Enter при редактировании текстового поля:

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

```xml
<Condition Name="CurrentPageTextBoxKeyEnterPressCondition" Type="KeyDownCondition" Assembly="Conditions">
  <Object Name="CurrentPageTextBox" />
  <Key Value="Enter" />
</Condition>
```

{% endcode %}

Создадим условия проверки введенного значение на удовлетворение минимального и максимального количества страниц:

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

```xml
<Condition Name="CurrentPageTextBoxNotLessMinCondition" Type="NotLessCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="CurrentPageTextBox" />
    </Item>
    <Item>1</Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>

<Condition Name="CurrentPageTextBoxNotGreaterMaxCondition" Type="NotGreaterCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="CurrentPageTextBox" />
    </Item>
    <Item>
      <Object Name="TotalPagesVariable" />
    </Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>
```

{% endcode %}

Создадим Execution, в котором будет отрабатывать событие нажатия клавиши Enter в текстовом поле **CurrentPageTextBox**:

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

```xml
<Execution>
  <ConditionExpression>
    <Condition Name="CurrentPageTextBoxKeyEnterPressCondition" />
  </ConditionExpression>
  <Commands>
    <If>
      <When>
        <And>
          <Condition Name="CurrentPageTextBoxNotGreaterMaxCondition" />
          <Condition Name="CurrentPageTextBoxNotLessMinCondition" />
        </And>
      </When>
      <Then>
        <Command Name="CurrentPageVariableValueSetCommand">
          <Object Name="CurrentPageTextBox" />
        </Command>
      </Then>
      <ElseIf>
        <When>
          <Not>
            <Condition Name="CurrentPageTextBoxNotLessMinCondition" />
          </Not>
        </When>
        <Then>
          <Command Name="CurrentPageVariableValueSetCommand">1</Command>
        </Then>
      </ElseIf>
      <ElseIf>
        <When>
          <Not>
            <Condition Name="CurrentPageTextBoxNotGreaterMaxCondition" />
          </Not>
        </When>
        <Then>
          <Command Name="CurrentPageVariableValueSetCommand">
            <Object Name="TotalPagesVariable" />
          </Command>
        </Then>
      </ElseIf>
    </If>
  </Commands>
</Execution>
```

{% endcode %}

### Запрос на просмотр  <a href="#sqlquery-to-view" id="sqlquery-to-view"></a>

В **ClientPrimaryGetDataConnection** добавим параметры **Limit** (количество записей на странице) и **Page** (номер текущей страницы).

Так же необходимо переделать фильтрацию архивных и актуальных записей. Для этого из колонки **Archive** в таблице **ClientDatabaseTable** удалим тэг `<Filter>`, и добавим в **ClientPrimaryGetDataConnection** одноименный параметр.

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

```xml
<DataConnection Name="ClientPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <SqlQuery Name="ClientSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="ClientId" />
      <Field Name="Name" />
      <Field Name="CityTitle" />
      <Field Name="Phone" />
      <Field Name="Archive" />
      <Field Name="Count" />
    </Fields>
    <Parameters>
      <Parameter NativeName="Archive">
        <Value>
          <Object Name="ArchiveFilterComboBox" />
        </Value>
      </Parameter>
      <Parameter NativeName="SearchKeywords" RefreshQuery="False">
        <Value>
          <Object Name="SearchKeywordsVariable" />
        </Value>
      </Parameter>
      <Parameter NativeName="Limit">
        <Value>
          <Object Name="ItemsPerPageComboBox" />
        </Value>
      </Parameter>
      <Parameter NativeName="Page">
        <Value>
          <Object Name="CurrentPageVariable" />
        </Value>
      </Parameter>
    </Parameters>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

В запрос **ClientSelectSqlQuery** добавим переменные для новых параметров:

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

```xml
<SqlQuery Name="ClientSelectSqlQuery">
  <Text>
    SELECT
      client.client_id AS "ClientId",
      client.title AS "Name",
      city.title AS "CityTitle",
      client.phone AS "Phone",
      client.archive AS "Archive",
      COUNT(*) OVER() AS "Count"
    FROM
      template.client
      LEFT JOIN template.city USING(city_id)
    WHERE
      ({Archive} ISNULL OR client.archive = {Archive}) AND
      ({SearchKeywords} ISNULL OR client.title ilike '%' || {SearchKeywords} ||'%')
    ORDER BY client.title
    LIMIT {Limit}
    OFFSET GREATEST(({Page} - 1) * {Limit}, 0);
  </Text>
</SqlQuery>
```

{% endcode %}

Запустите проект и проверьте загрузку формы и работу постраничного просмотра списка клиентов.

### Самостоятельно <a href="#self-work-pagination" id="self-work-pagination"></a>

Реализуйте постраничный просмотр на главной форме.

<figure><img src="/files/T5XGZhGceRqFoxxoSx42" alt=""><figcaption></figcaption></figure>

## Загрузка файлов <a href="#uploading-files" id="uploading-files"></a>

Дальше рассмотрим возможность загрузки файлов на сервер, скачивание и просмотр файлов через клиентское приложение.

В карточке клиента реализуем возможность добавлять фото, которое будем сохранять на сервер.&#x20;

### База данных <a href="#database" id="database"></a>

В таблицу **template.client** добавим колонку **photo\_file\_id**, которую через внешний ключ привяжем к таблице **public.file**:

```sql
ALTER TABLE template.client
  ADD COLUMN photo_file_id bigint;
ALTER TABLE template.client
  ADD CONSTRAINT fk_file_client_id FOREIGN KEY (photo_file_id) REFERENCES public.file (file_id) ON UPDATE NO ACTION ON DELETE NO ACTION;
```

В таблице public.file хранится вся необходимая информация о загруженном файле: имя файла без расширения (name), полный путь до файла на сервере (path), дата загрузки (date) и уникальный guid-идентификатор.

### Подготовка формы

Начнем с того, что на форму карточки клиентов добавим графический элемент [PictureBox](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/picturebox), в котором будет отображаться фото клиента.

Перейдем в файл TemplateClientEdit.xml&#x20;

Внутри панели **ContentPanel** создадим две панели: **MainPanel**, в которую перенесем все имеющиеся поля на форме, и **PhotoPanel**, в которой будут располагаться объекты для работы с изображением:

<figure><img src="/files/SN5G6RyQz04ZDwOUdpxY" alt=""><figcaption></figcaption></figure>

Скачайте архив с фото, которое будем использовать в качестве заглушки, и распакуйте его в папку **\Template\Projects\1. Template\Forms\Images**.

{% file src="/files/hl7ZLnz2f9BoSVCib2is" %}

Добавим в PhotoPanel объект типа PictureBox:

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

```xml
<MyObject Name="PhotoPictureBox" Type="PictureBox" Assembly="BaseControls">
  <Change User="True" Source="False" ValueSet="False" />
  <Top>5</Top>
  <Left>10</Left>
  <Height>
    <Calculate>
      <Expression>{0} - {1}*2 - 5</Expression>
      <Items>
        <Item>
          <Object Name="PhotoEditButton">
            <Property Name="Top" />
          </Object>
        </Item>
        <Item>
          <Object Name="PhotoPictureBox">
            <Property Name="Top" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Height>
  <Width>
    <Calculate>
      <Expression>{0} - {1}*2</Expression>
      <Items>
        <Item>
          <Object Name="PhotoPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="PhotoPictureBox">
            <Property Name="Left" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Width>
  <TabIndex>1</TabIndex>
  <Image> </Image>
  <SizeMode>Zoom</SizeMode>
  <NullImage>Images\no_photo.png</NullImage>
  <ErrorImage>Images\no_photo.png</ErrorImage>
</MyObject>
```

{% endcode %}

В качестве значения тэга `<Image>` мы будем передавать ссылку с GUID файла, расположенного на сервере. По этой ссылке объект PictureBox самостоятельно скачает файл с сервера.

В тэгах `<NullImage>` и `<ErrorImage>` указан относительный путь до изображения, которое будет отображаться пользователю, если тэг `<Image>` имеет значение NULL, или изображение загружено с ошибкой.

Скорректируем запрос **ClientByIdSelectSqlQuery**, добавив в него поля **PhotoGuid** и **PhotoGuidPath**. Первое поле будет содержать чистый guid файла, который нам понадобится для скачивания изображения перед открытием на клиентской машине. Второе поле - guid-ссылка на файл, которую будем передавать в PhotoPictureBox.

<pre class="language-xml" data-title="Template.xml"><code class="lang-xml">&#x3C;SqlQuery Name="ClientByIdSelectSqlQuery">
  &#x3C;Text>
<strong>    SELECT
</strong>      client.title AS "Name",
      client.city_id AS "CityId",
      client.date_of_birth AS "DateOfBirth",
      client.email AS "Email",
      client.phone AS "Phone",
      F.guid AS "PhotoGuid",
      'guid://' || F.guid AS "PhotoGuidPath"
    FROM
      template.client
      LEFT JOIN public.file F ON client.photo_file_id = F.file_id
    WHERE
      client.client_id = {ClientId};
  &#x3C;/Text>
&#x3C;/SqlQuery>
</code></pre>

Добавим новые поля в **ClientPrimaryGetDataConnection** и укажем PhotoGuidPath в качестве значения тэга `<Image>` PhotoPictureBox.

Добавим кнопки добавления и удаления изображения:

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

```xml
<MyObject Name="PhotoEditButton" Type="Button" Assembly="BaseControls">
  <Bottom>
    <Calculate>
      <Expression>{0} - 10</Expression>
      <Items>
        <Item>
          <Object Name="PhotoPanel">
            <Property Name="Height" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Bottom>
  <Left>10</Left>
  <Height>22</Height>
  <Width>
    <Calculate>
      <Expression>Ceiling(({0} - {1}*2 - 5)/2)</Expression>
      <Items>
        <Item>
          <Object Name="PhotoPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="PhotoEditButton">
            <Property Name="Left" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Width>
  <TabIndex>2</TabIndex>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>1</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <BackgroundImage>Images\16x16\pencil.png</BackgroundImage>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <Hint>Редактировать фотографию</Hint>
  <Commands> </Commands>
</MyObject>

<MyObject Name="PhotoDeleteButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Object Name="PhotoEditButton">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Calculate>
      <Expression>{0} + 5</Expression>
      <Items>
        <Item>
          <Object Name="PhotoEditButton">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Height>
    <Object Name="PhotoEditButton">
      <Property Name="Height" />
    </Object>
  </Height>
  <Width>
    <Object Name="PhotoEditButton">
      <Property Name="Width" />
    </Object>
  </Width>
  <TabIndex>3</TabIndex>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>1</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <BackgroundImage>Images\16x16\close.png</BackgroundImage>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <Hint>Удалить фотографию</Hint>
  <Commands> </Commands>
</MyObject>
```

{% endcode %}

Запустите проект и проверьте расположение объектов на форме.

<figure><img src="/files/RydxW9J2nny3k1G9j4Yk" alt=""><figcaption></figcaption></figure>

### Редактирование изображения <a href="#editing-image" id="editing-image"></a>

Изображение будем выбирать с помощью диалогового окна выбора файлов, которое будем открывать по команде типа [FileDialogShowCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/file_dialog_show_command). Создадим такую команду:

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

```xml
<Command Name="PhotoFileDialogShowCommand" Type="FileDialogShowCommand" Assembly="Commands">
  <Title>Выбор фотографии клиента</Title>
  <Type Value="Open" />
  <Filter>Photo Files|*.jpeg;*.jpg;*.png</Filter>
</Command>
```

{% endcode %}

Добавьте вызов этой команды на кнопку PhotoEditButton.

Обращаясь к параметру **FullPath** результата выполнения команды **FileDialogShowCommand**, мы получим полный путь до выбранного файла, который и передадим в наш объект **PhotoPictureBox** через set-проперти [Image](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/picturebox#set_image):

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

```xml
<Command Name="PhotoPictureBoxPhotoValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="PhotoPictureBox">
    <Property Name="Image">
      <Command Name="PhotoFileDialogShowCommand" Parameter="FullPath" />
    </Property>
  </Object>
</Command>
```

{% endcode %}

{% hint style="info" %}
Команда FileDialogShowCommand возвращает словарь со следующими элементами:

* OK - признак того, что в диалоговом окне пользователем был выбран хотя бы 1 файл;
* Path - массив путей до папок, в которых расположены выбранные файлы, или путь до папки, в котором расположен 1 выбранный файл;
* FileName - массив имен выбранных файлов (только имена, без расширения) или имя 1 выбранного файла;
* FileNameExtension - массив расширений выбранных файлов (с точкой в начале) или расширение 1 выбранного файла;
* FullFileName - массив полных имен выбранных файлов (имя и расширение) или полное имя 1 выбранного файла;
* FullPath - массив полных путей до выбранных файлов (путь и полное имя) или полный путь до 1 выбранного файла.
  {% endhint %}

Создадим Execution для обработки результата выполнения команды выбора файла:

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

```xml
<Execution>
  <ConditionExpression>
    <Command Name="PhotoFileDialogShowCommand" Parameter="OK" />
  </ConditionExpression>
  <Commands>
    <Command Name="PhotoPictureBoxPhotoValueSetCommand" />
    <Command Name="FormChangedValueSetCommand">True</Command>
  </Commands>
</Execution>
```

{% endcode %}

<details>

<summary>FormChangedValueSetCommand</summary>

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

```xml
<Command Name="FormChangedValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Form>
    <Property Name="FormChanged">
      <Input />
    </Property>
  </Form>
</Command>
```

{% endcode %}

</details>

### Удаление изображения <a href="#deleting-image" id="deleting-image"></a>

Создадим условия для проверки наличия пользовательского изображения в поле PhotoPictureBox. Для этого через get-проперти [CurrentImageSource](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/picturebox#get_current_image_source) будем получать источник отображаемого изображения и сравнивать его со значениями, возвращаемыми get-проперти [NullImage](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/picturebox#get_null_image) и [ErrorImage](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/picturebox#get_error_image).

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

```xml
<Condition Name="PhotoPictureBoxIsNullOrEmptyCondition" Type="IsNullOrEmptyCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="PhotoPictureBox">
        <Property Name="CurrentImageSource" />
      </Object>
    </Item>
  </Items>
</Condition>

<Condition Name="PhotoPictureBoxIsNotNullImageCondition" Type="NotEqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="PhotoPictureBox">
        <Property Name="CurrentImageSource" />
      </Object>
    </Item>
    <Item>
      <Object Name="PhotoPictureBox">
        <Property Name="NullImage" />
      </Object>
    </Item>
  </Items>
</Condition>

<Condition Name="PhotoPictureBoxIsNotErrorImageCondition" Type="NotEqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="PhotoPictureBox">
        <Property Name="CurrentImageSource" />
      </Object>
    </Item>
    <Item>
      <Object Name="PhotoPictureBox">
        <Property Name="ErrorImage" />
      </Object>
    </Item>
  </Items>
</Condition>

<Condition Name="PhotoPictureBoxIsNotEmptyCondition" Type="NestedCondition" Assembly="Conditions">
  <ConditionExpression>
    <And>
      <Not>
        <Condition Name="PhotoPictureBoxIsNullOrEmptyCondition" />
      </Not>
      <Condition Name="PhotoPictureBoxIsNotNullImageCondition" />
      <Condition Name="PhotoPictureBoxIsNotErrorImageCondition" />
    </And>
  </ConditionExpression>
</Condition>
```

{% endcode %}

Для удобства использования объединили обе проверки в одно условие PhotoPictureBoxIsNotEmptyCondition, которое укажем на кнопке PhotoDeleteButton в тэге `<Enabled>`.

Создадим команду для удаления пользовательского изображения из PhotoPictureBox.&#x20;

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

```xml
<Command Name="PhotoPictureBoxNullValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="PhotoPictureBox">
    <Property Name="Image">
      <Object Name="PhotoPictureBox">
        <Property Name="NullImage" />
      </Object>
    </Property>
  </Object>
</Command>
```

{% endcode %}

Добавьте вызов этой команды на кнопку PhotoDeleteButton.

{% hint style="info" %}
Для удаления файла с сервера используйте команду [DeleteFileCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/delete_file_command).
{% endhint %}

### Сохранение изображения <a href="#saving-image" id="saving-image"></a>

Для отправки файла на сервер создадим команда типа [UploadFileCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/upload_file_command):

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

```xml
<Command Name="PhotoPictureBoxUploadFileCommand" Type="UploadFileCommand" Assembly="Commands">
  <FileName>
    <Object Name="PhotoPictureBox">
      <Property Name="CurrentImageSource" />
    </Object>
  </FileName>
</Command>
```

{% endcode %}

Серверная часть сохранит файл в хранилище, имя которого указывается в тэге [`<Storage>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/upload_file_command#storage). Так как у нас этот тэг отсутствует, то в качестве каталога для сохранения файлов будет использоваться хранилище с именем **Default**.&#x20;

{% hint style="info" %}
Имена хранилищ и пути до их папок указываются в файле настроек серверной части ([appsettings.json](https://wfsys.gitbook.io/wt-practice/beginning/project/configuration_files/appsettings.json)):

```json
"Storages": {
  "Default": {
    "Path": "Upload",
    "Format": "yyyy\\\\MM\\\\dd"
  }
}
```

Хранилище **Default** привязано к папке **Upload** в каталоге, куда была развернута серверная часть приложения. В поле "Format" задается формат вложенных папок.
{% endhint %}

При успешном сохранении сервер сгенерирует уникальный guid-идентификатор, который вернется клиенту результатом команды UploadFileCommand.

Необходимая информация о загруженном файле будет сохранена в таблицу **public.file**.

Скорректируем команду SaveSequentialCommand, добавив вызов команды на передачу файла на сервер:

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

```xml
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <If>
      <When>
        <Condition Name="PhotoPictureBoxIsNotEmptyCondition" />
      </When>
      <Then>
        <Command Name="PhotoPictureBoxUploadFileCommand" />
      </Then>
    </If>
    <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 %}

Добавим в ClientInsertSetDataConnection и ClientUpdateSetDataConnection параметр PhotoGuid, в котором будем передавать guid-идентификатор загруженного файла:

```xml
<Parameter NativeName="PhotoGuid">
  <Value>
    <Switch>
      <Case>
        <When>
          <Condition Name="PhotoPictureBoxIsNotEmptyCondition" />
        </When>
        <Then>
          <Command Name="PhotoPictureBoxUploadFileCommand" />
        </Then>
      </Case>
    </Switch>
  </Value>
</Parameter>
```

Скорректируем запросы:

<details>

<summary>ClientInsertSqlQuery</summary>

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

```xml
<SqlQuery Name="ClientInsertSqlQuery">
  <Text>
    INSERT INTO template.client(
      city_id,
      date_of_birth,
      title,
      email,
      phone,
      photo_file_id
    )
    VALUES (
      {CityId},
      {DateOfBirth},
      {Name},
      {Email},
      {Phone},
      (SELECT F.file_id FROM public.file F WHERE F.guid = {PhotoGuid})
    )
    RETURNING client_id;
  </Text>
</SqlQuery>
```

{% endcode %}

</details>

<details>

<summary>ClientUpdateSqlQuery</summary>

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

```xml
<SqlQuery Name="ClientUpdateSqlQuery">
  <Text>
    UPDATE template.client
    SET
      city_id = {CityId},
      date_of_birth = {DateOfBirth},
      title = {Name},
      email = {Email},
      phone = {Phone},
      photo_file_id = (SELECT F.file_id FROM public.file F WHERE F.guid = {PhotoGuid})
    WHERE
      client_id = {ClientId};
  </Text>
</SqlQuery>
```

{% endcode %}

</details>

### Самостоятельно <a href="#self-work-save-file" id="self-work-save-file"></a>

В таком сохранении есть один большой недочет: при сохранении изменений в других полях (например, изменили дату рождения), если у клиента ранее была сохранена фотография, то команда PhotoPictureBoxUploadFileCommand будет выполняться повторно. Это приведет к очередному сохранению файла на сервер, а значит добавлению копии файла и перезаписи *photo\_file\_id* у записи в *template.client*. Придумайте и реализуйте механизм, блокирующий перезапись файла, если файл не изменялся.

Учтите один момент: сейчас в качестве параметра PhotoGuid передается результат команды PhotoPictureBoxUploadFileCommand, если она не будет вызываться, то параметре будет передаваться пустое значение, что приведет к удалению ссылки на фото у записи в *template.client*.

### Просмотр изображения <a href="#view-image" id="view-image"></a>

Теперь нам необходимо реализовать просмотр изображения по двойному клику по объекту PhotoPictureBox. При этом будем различать две ситуации в зависимости от источника изображения:

* из каталога на локальной машине;
* по guid-ссылке файла на сервере.

#### Путь до файла на локальной машине <a href="#path-to-file-on-local-machine" id="path-to-file-on-local-machine"></a>

Когда мы добавляем новое изображение в PhotoPictureBox, get-проперти [CurrentImageSource](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/picturebox#get_current_image_source) возвращает полный путь до файла на локальной машине. В таком случае мы можем открыть изображение с помощью команды типа [ApplicationRunCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/application_run_command), которая будет запускать подходящее приложение относительно переданного в тэг `<Application>` файла.

Создадим такую команду:

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

```xml
<Command Name="PhotoApplicationRunCommand" Type="ApplicationRunCommand" Assembly="Commands">
  <Condition Name="PhotoPictureBoxIsNotEmptyCondition" />
  <Application>
    <Object Name="PhotoPictureBox">
      <Property Name="CurrentImageSource" />
    </Object>
  </Application>
</Command>
```

{% endcode %}

Условие **PhotoPictureBoxIsNotEmptyCondition**, указанное во вложенном тэге `<Condition>`, ограничит выполнение команды, если в объекте нет пользовательского изображения.

#### По guid-ссылке файла <a href="#by-file-guid" id="by-file-guid"></a>

Если в PhotoPictureBox передается guid-ссылка на файл, расположенный на сервере, то мы не можем воспользоваться командой ApplicationRunCommand - она не распознает эту ссылку. В этом случае нам нужно скачать файл, воспользовавшись командой типа [DownloadFileCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/download_file_command).

Создадим такую команду:

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

```xml
<Command Name="PhotoDownloadFileCommand" Type="DownloadFileCommand" Assembly="Commands">
  <Condition Name="PhotoPictureBoxIsNotEmptyCondition" />
  <FileGuid>
    <DataConnection SourceDataConnection="ClientPrimaryGetDataConnection">
      <Fields>
        <Field Name="PhotoGuid"/>
      </Fields>
    </DataConnection>
  </FileGuid>
  <AllowOverwrite>True</AllowOverwrite>
</Command>
```

{% endcode %}

После скачивания файла с сервера команда сама откроет этот файл в подходящем приложении.

#### Реализация <a href="#implementation" id="implementation"></a>

Создадим условие типа [StartsWithCondition](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/conditions/comparison_condition/starts_with_condition), чтобы различать guid-ссылки на файла:

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

```xml
<Condition Name="CurrentImageSourceIsGuidStartsWithCondition" Type="StartsWithCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="PhotoPictureBox">
        <Property Name="CurrentImageSource" />
      </Object>
    </Item>
    <Item>guid://</Item>
  </Items>
</Condition>
```

{% endcode %}

Создадим Execution для обработки двойного клика по PhotoPictureBox для просмотра изображения в стороннем приложении:

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

```xml
<Execution>
  <ConditionExpression>
    <Condition Name="PhotoPictureBoxDoubleClickCondition" />
  </ConditionExpression>
  <Commands>
    <If>
      <When>
        <Condition Name="CurrentImageSourceIsGuidStartsWithCondition" />
      </When>
      <Then>
        <Command Name="PhotoDownloadFileCommand" />
      </Then>
      <Else>
        <Command Name="PhotoApplicationRunCommand" />
      </Else>
    </If>
  </Commands>
</Execution>
```

{% endcode %}

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

В этом уроке мы рассмотрели паттерн Pagination, применение которого позволяет просматривать большой объем записей, разбив их на страницы. Познакомились с объектом  [PictureBox](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/picturebox) для отображения изображений на форме, а также рассмотрели команды  [UploadFileCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/upload_file_command) и [DownloadFileCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/download_file_command) для загрузки файлов на сервер и скачивания их на клиентскую машину соответственно.

Это был заключительный урок в базовом блоке. В следующих уроках уделим внимание режимам загрузки данных и многопользовательскому режиму.

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

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

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wfsys.gitbook.io/wt-practice/main/lesson_pagination.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
