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

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

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

Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.

При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.

Инструкция по подключению шаблонов находится по ссылке.

Постраничный просмотр

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

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

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

PagePanel
TemplateClientList.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>

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

Количество записей в ответе от сервера

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

Template.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>

Создадим условие для проверки количества строк в ClientPrimaryGetDataConnection, используя для этого get-проперти Count:

TemplateClientList.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>

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

TemplateClientList.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>

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

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

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

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

TotalItemsLabel
TemplateClientList.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>

Переход между страницами

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

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

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

CurrentPageTextBox
TemplateClientList.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>

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

TemplateClientList.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>

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

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

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

TotalPagesLabel
TemplateClientList.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>

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

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

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

TemplateClientList.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>

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

TemplateClientList.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>

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

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

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

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

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

FirstPageButton
TemplateClientList.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>
PrevPageButton
TemplateClientList.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>
NextPageButton
TemplateClientList.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>
LastPageButton
TemplateClientList.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>

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

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

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

TemplateClientList.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>

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

TemplateClientList.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>

Запрос на просмотр

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

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

TemplateClientList.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>

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

Template.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>

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

Самостоятельно

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

Загрузка файлов

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

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

База данных

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

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, в котором будет отображаться фото клиента.

Перейдем в файл TemplateClientEdit.xml

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

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

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

TemplateClientEdit.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>

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

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

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

Template.xml
<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",
      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};
  </Text>
</SqlQuery>

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

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

TemplateClientEdit.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>

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

Редактирование изображения

Изображение будем выбирать с помощью диалогового окна выбора файлов, которое будем открывать по команде типа FileDialogShowCommand. Создадим такую команду:

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

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

Обращаясь к параметру FullPath результата выполнения команды FileDialogShowCommand, мы получим полный путь до выбранного файла, который и передадим в наш объект PhotoPictureBox через set-проперти Image:

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

Команда FileDialogShowCommand возвращает словарь со следующими элементами:

  • OK - признак того, что в диалоговом окне пользователем был выбран хотя бы 1 файл;

  • Path - массив путей до папок, в которых расположены выбранные файлы, или путь до папки, в котором расположен 1 выбранный файл;

  • FileName - массив имен выбранных файлов (только имена, без расширения) или имя 1 выбранного файла;

  • FileNameExtension - массив расширений выбранных файлов (с точкой в начале) или расширение 1 выбранного файла;

  • FullFileName - массив полных имен выбранных файлов (имя и расширение) или полное имя 1 выбранного файла;

  • FullPath - массив полных путей до выбранных файлов (путь и полное имя) или полный путь до 1 выбранного файла.

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

TemplateClientEdit.xml
<Execution>
  <ConditionExpression>
    <Command Name="PhotoFileDialogShowCommand" Parameter="OK" />
  </ConditionExpression>
  <Commands>
    <Command Name="PhotoPictureBoxPhotoValueSetCommand" />
    <Command Name="FormChangedValueSetCommand">True</Command>
  </Commands>
</Execution>
FormChangedValueSetCommand
TemplateClientEdit.xml
<Command Name="FormChangedValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Form>
    <Property Name="FormChanged">
      <Input />
    </Property>
  </Form>
</Command>

Удаление изображения

Создадим условия для проверки наличия пользовательского изображения в поле PhotoPictureBox. Для этого через get-проперти CurrentImageSource будем получать источник отображаемого изображения и сравнивать его со значениями, возвращаемыми get-проперти NullImage и ErrorImage.

TemplateClientEdit.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>

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

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

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

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

Для удаления файла с сервера используйте команду DeleteFileCommand.

Сохранение изображения

Для отправки файла на сервер создадим команда типа UploadFileCommand:

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

Серверная часть сохранит файл в хранилище, имя которого указывается в тэге <Storage>. Так как у нас этот тэг отсутствует, то в качестве каталога для сохранения файлов будет использоваться хранилище с именем Default.

Имена хранилищ и пути до их папок указываются в файле настроек серверной части (appsettings.json):

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

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

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

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

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

TemplateClientEdit.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>

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

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

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

ClientInsertSqlQuery
Template.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>
ClientUpdateSqlQuery
Template.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>

Самостоятельно

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

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

Просмотр изображения

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

  • из каталога на локальной машине;

  • по guid-ссылке файла на сервере.

Путь до файла на локальной машине

Когда мы добавляем новое изображение в PhotoPictureBox, get-проперти CurrentImageSource возвращает полный путь до файла на локальной машине. В таком случае мы можем открыть изображение с помощью команды типа ApplicationRunCommand, которая будет запускать подходящее приложение относительно переданного в тэг <Application> файла.

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

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

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

По guid-ссылке файла

Если в PhotoPictureBox передается guid-ссылка на файл, расположенный на сервере, то мы не можем воспользоваться командой ApplicationRunCommand - она не распознает эту ссылку. В этом случае нам нужно скачать файл, воспользовавшись командой типа DownloadFileCommand.

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

TemplateClientEdit.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>

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

Реализация

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

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

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

TemplateClientEdit.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>

Итоги

В этом уроке мы рассмотрели паттерн Pagination, применение которого позволяет просматривать большой объем записей, разбив их на страницы. Познакомились с объектом PictureBox для отображения изображений на форме, а также рассмотрели команды UploadFileCommand и DownloadFileCommand для загрузки файлов на сервер и скачивания их на клиентскую машину соответственно.

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

Ответы

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

Last updated