Урок 3. Форма списка

Список

SQL-запрос

На основе запроса OrderSelectSqlQuery, который описан в серверном xml-файле, создадим запрос для получения списка заказов для мобильного приложения:

Template.xml
<SqlQuery Name="AppOrderSelectSqlQuery">
  <Text>
    WITH _filter AS (
      SELECT
        date_trunc('week', CURRENT_DATE::timestamp) AS start_date,
        date_trunc('week', CURRENT_DATE::timestamp) + interval '7d - 1s' AS end_date
    ), _order AS (
      SELECT
        O.order_id,
        O.client_id,
        C.city_id,
        O.order_number,
        O.order_date
      FROM
        template.order O
        LEFT JOIN template.client C USING(client_id)
        LEFT JOIN _filter ON true
      WHERE
        O.added AND
        NOT O.deleted AND
        O.order_date BETWEEN convert_date_filter(_filter.start_date)
                         AND convert_date_filter(_filter.end_date)
    ), _order_position AS (
      SELECT
        O.order_id,
        SUM(OP.quantity * OP.unit_price) AS total_cost,
        COUNT(O.order_id) AS position_count
      FROM
        _order O
        JOIN template.order_position OP USING(order_id)
        LEFT JOIN template.material M USING(material_id)
      GROUP BY O.order_id
    ), _payment AS (
      SELECT
        O.order_id,
        SUM(C.summ) AS total_payment_summ
      FROM
        _order O
        JOIN template.order_payment OP USING(order_id)
        JOIN template.cash C USING(cash_id)
      WHERE NOT C.deleted
      GROUP BY O.order_id
    )
    
    SELECT
      O.order_id AS "OrderId",
      '№ ' || O.order_number AS "OrderNumber",
      'от ' || to_char(convert_date_to_user_timezone(O.order_date), 'dd.mm.yyyy') AS "OrderDate",
      client.title AS "ClientTitle",
      OP.position_count AS "PositionCount",
      'Сумма ' || trim(to_char(COALESCE(OP.total_cost, 0), '999G999G999D00') || ' руб.') AS "TotalOrderCost",
      (COALESCE(OP.total_cost, 0) - COALESCE(P.total_payment_summ, 0)) <![CDATA[<=]]> 0 AS "IsPaid"
    FROM
      _order O
      LEFT JOIN _order_position OP USING(order_id)
      LEFT JOIN _payment P USING(order_id)
      LEFT JOIN template.client USING(client_id)
    ORDER BY O.order_number ASC;
  </Text>
</SqlQuery>

В качестве периода отображения заказов будем использовать текущую неделю - позже реализуем фильтр по датам, как делали на главной форме в приложении для ПК.

Номер и дату заказа (OrderNumber и OrderDate) выводим разными строками, заранее задав им нужный формат. Обратите внимание, что дату заказа сразу приводим к часовой зоне пользователя, чтобы она отображалась корректно. Как вы помните, при передаче даты со временем в формате строки, платформа не делает автоматическое приведение временных зон. И без ручного приведения временной зоны, даты со временем останется во временной зоне сервера. Работа с датами со временем подробно рассматривается в статье Временные зоны.

В запрос добавили поле PositionCount, хранящее количество позиций заказа, и поле IsPaid - признак полной оплаты заказа. Теперь возвращаем имена клиентов вместо идентификаторов, т.к. в отличии от таблицы DatabaseTable список CollectionView не поддерживает Substitution.

Перейдем в файл главной формы (TemplateMainForm.xml).

Самостоятельно создайте соответствующий PrimaryGetDataConnection.

Теперь создадим объект типа CollectionView, который представляет данные в виде списка карточек:

TemplateMainForm.xml
<MyObject Name="OrderCollectionView" Type="CollectionView" Assembly="WorkflowForms">
  <Top>10</Top>
  <Left>10</Left>
  <Height>
    <Calculate>
      <Expression>{0} - {1} - 10</Expression>
      <Items>
        <Item>
          <Object Name="ContentPanel">
            <Property Name="Height" />
          </Object>
        </Item>
        <Item>
          <Object Name="OrderCollectionView">
            <Property Name="Top" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Height>
  <Width>
    <Calculate>
      <Expression>{0} - {1}*2</Expression>
      <Items>
        <Item>
          <Object Name="ContentPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="OrderCollectionView">
            <Property Name="Left" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Width>
  <SourceDataConnection Name="OrderPrimaryGetDataConnection" />
  <EmptyText>Список заказов пуст</EmptyText>
  <VerticalCardSpacing>7</VerticalCardSpacing>
  <CardStyle>
    <BackColor>
      <Switch>
        <Case>
          <When>
            <Condition Name="AppThemeDarkEqualCondition" />
          </When>
          <Then>VeryDarkGray</Then>
        </Case>
        <Case>WhiteSmoke</Case>
      </Switch>
    </BackColor>
    <BorderColor>
      <Switch>
        <Case>
          <When>
            <Condition Name="AppThemeDarkEqualCondition" />
          </When>
          <Then>VeryDarkGray</Then>
        </Case>
        <Case>WhiteSmoke</Case>
      </Switch>
    </BorderColor>
    <BorderCorner>7</BorderCorner>
    <Padding Left="15" Top="0" Right="15" Bottom="0" />
  </CardStyle>
  <CardTemplate>
  
  </CardTemplate>
</MyObject>

Рассмотрим основные моменты:

В тэге <SourceDataConnection> указывается источник данных, каждой записи (строке) которого будет соответствовать отдельная карточка.

Тэг <EmptyText> содержит текст-заглушку, который будет отображаться, если в списке нет записей.

В тэге <VerticalCardSpacing> задается интервал между карточками.

В тэге <CardStyle> настраивается стиль карточки списка. В тэге <BackColor> задали цвет фона карточки, а в тэге <BorderColor> - цвет границ карточки. Оба цвета зависят от выбранной темы оформления системы. Так же можно задать радиус скругления углов карточки.

В тэге <CardTemplate> описываются элементы шаблона карточки списка. К таким элементам относятся текстовые поля, иконки, кнопки и прочее. Далее будем настраивать шаблон карточек.

Шаблон карточки

Добавим на форму описание цветов, которые понадобятся при описании шаблона карточки:

TemplateMainForm.xml
<Color Name="StrongBlue" Red="39" Green="69" Blue="195" Alpha="255" />
<Color Name="SoftBlue" Red="68" Green="134" Blue="218" Alpha="255" />
<Color Name="VerySoftBlue" Red="187" Green="222" Blue="251" Alpha="255" />

А так же стили шрифтов для текста на карточках списка заказов:

TemplateMainForm.xml
<FontStyle Name="CollectionTitleFont" Font="Arial" Size="16" />
<FontStyle Name="CollectionSubTitleFont" Font="Arial" Size="15" />
<FontStyle Name="CollectionStatusFont" Font="Arial" Size="14" />

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

Номер заказа - это простое текстовое поле, для него будем использовать элемент типа TemplateItemLabel:

<TemplateItem Type="TemplateItemLabel">
  <Top>0</Top>
  <Left>0</Left>
  <Text Binding="OrderNumber" />
  <FontStyle>CollectionTitleFont</FontStyle>
  <ForeColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="AppThemeDarkEqualCondition" />
        </When>
        <Then>SoftBlue</Then>
      </Case>
      <Case>StrongBlue</Case>
    </Switch>
  </ForeColor>
</TemplateItem>

В тэгах <Top> и <Left> задаются абсолютные значения отступов от границ контейнера, с учетом внутренних отступов контейнера для его содержимого.

В тэге <Text> указывается текст, отображаемый в поле на карточке. При этом можно указать текст значением тэга, тогда такой текст будет одинаков на всех карточках. А можно указать атрибут Binding с именем одного из полей источника данных из тэга <SourceDataConnection>, в таком случае значение будет индивидуально для каждой карточки. Значение тэга <Text> и атрибут Binding вместе не работают - предпочтение отдается атрибуту.

В тэгах <FontStyle> и <ForeColor> настраиваем стиль текста, его цвет и размер.

Для дата заказа также будем использовать элемент типа TemplateItemLabel:

<TemplateItem Type="TemplateItemLabel">
  <Top>0</Top>
  <Left>1</Left>
  <Text Binding="OrderDate" />
  <AbsoluteLayoutFlags>XProportional</AbsoluteLayoutFlags>
</TemplateItem>

Так как поле должно отображаться в правом верхнем углу, то координату <Left> переведем из абсолютного значения в пропорциональное. Для этого используем тэг <AbsoluteLayoutFlags> со значением XProportional. Теперь в тэге <Left> указываем не величину сдвига, а коэффициент, который будет использоваться для расчета координаты по формуле:

координата_Х = (ширина_контейнера - ширина_элемента) * пропорциональное_значение_Х

Аналогичным образом можно задавать пропорциональное значение для координаты <Top> - YProportional, а так же одновременно для координат <Top> и <Left> - PositionProportional. Пропорции можно задавать для ширины и высоты элемента - WidthProportional и HeightProportional соответственно, а так же одновременно для высоты и ширины - SizeProportional. Если необходимо для всех четырех значений использовать пропорциональные, то следует указывать All.

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

Проверьте. как будет отображаться экран, если в списке заказов не будет записей:

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

Под номером и датой заказа добавим поля с именем клиента и суммой заказа:

<TemplateItem Type="TemplateItemLabel">
  <Top>30</Top>
  <Left>0</Left>
  <Text Binding="ClientTitle" />
  <FontStyle>CollectionSubTitleFont</FontStyle>
</TemplateItem>

<TemplateItem Type="TemplateItemLabel">
  <Top>55</Top>
  <Left>0</Left>
  <Text Binding="TotalOrderCost" />
  <FontStyle>CollectionSubTitleFont</FontStyle>
</TemplateItem>

Значение координаты <Top> придется подобрать вручную.

Для статуса "Оплачен" создадим элемент, который будет отображаться с правой стороны карточки:

<TemplateItem Type="TemplateItemLabel">
  <Top>0.5</Top>
  <Left>1</Left>
  <Height>25</Height>
  <AbsoluteLayoutFlags>PositionProportional</AbsoluteLayoutFlags>
  <Visible Binding="IsPaid"/>
  <Text>Оплачен</Text>
  <ForeColor>StrongBlue</ForeColor>
  <FontStyle>CollectionStatusFont</FontStyle>
  <BackColor>VerySoftBlue</BackColor>
  <BorderCorner>7</BorderCorner>
  <TextAlignment Vertical="Center" Horizontal="Center" />
  <Padding Left="15" Top="0" Right="15" Bottom="0" />
</TemplateItem>

В этот раз для тэга <AbsoluteLayoutFlags> используем значение PositionProportional, чтобы в тэге <Top> так же указывать пропорциональное значения. Таким образом элемент будет расположен с правого края по середине вертикали.

В качестве условия видимости элемента в атрибуте Binding тэга <Visible> укажем поле IsPaid.

Чтобы статус визуально выделялся, зададим цвет фона текста в тэге <BackColor> и радиус скругления углов фона в тэге <BorderCorner>. Зададим высоту элемента в 25 единиц.

Стоит помнить, что по умолчанию текст всегда выравнивается по верхней части своего блока. Чтобы текст был по центру блока, используем тэг <TextAlignment>, с указанием правил выравнивания для вертикали и горизонтали.

Тэг <Padding> добавит для текста внутренние отступы от границ фона - так блок будет выглядеть симпатичнее. Отступы будут только слева и справа.

Отлично! Запустим приложение, чтобы проверить отображение всех элементов карточки.

Полный код объекта OrderCollectionView
TemplateMainForm.xml
<MyObject Name="OrderCollectionView" Type="CollectionView" Assembly="WorkflowForms">
  <Top>10</Top>
  <Left>10</Left>
  <Height>
    <Calculate>
      <Expression>{0} - {1} - 10</Expression>
      <Items>
        <Item>
          <Object Name="ContentPanel">
            <Property Name="Height" />
          </Object>
        </Item>
        <Item>
          <Object Name="CollectionView">
            <Property Name="Top" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Height>
  <Width>
    <Calculate>
      <Expression>{0} - {1}*2</Expression>
      <Items>
        <Item>
          <Object Name="ContentPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="CollectionView">
            <Property Name="Left" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Width>
  <SourceDataConnection Name="OrderPrimaryGetDataConnection" />
  <EmptyText>Список заказов пуст</EmptyText>
  <VerticalCardSpacing>7</VerticalCardSpacing>
  <CardStyle>
    <BackColor>
      <Switch>
        <Case>
          <When>
            <Condition Name="AppThemeDarkEqualCondition" />
          </When>
          <Then>VeryDarkGray</Then>
        </Case>
        <Case>WhiteSmoke</Case>
      </Switch>
    </BackColor>
    <BorderColor>
      <Switch>
        <Case>
          <When>
            <Condition Name="AppThemeDarkEqualCondition" />
          </When>
          <Then>VeryDarkGray</Then>
        </Case>
        <Case>WhiteSmoke</Case>
      </Switch>
    </BorderColor>
  </CardStyle>
  <CardTemplate>    
    <TemplateItem Type="TemplateItemLabel">
      <Top>0</Top>
      <Left>0</Left>
      <Text Binding="OrderNumber" />
      <FontStyle>CollectionTitleFont</FontStyle>
      <ForeColor>
        <Switch>
          <Case>
            <When>
              <Condition Name="AppThemeDarkEqualCondition" />
            </When>
            <Then>SoftBlue</Then>
          </Case>
          <Case>StrongBlue</Case>
        </Switch>
      </ForeColor>
    </TemplateItem>
    <TemplateItem Type="TemplateItemLabel">
      <Top>0</Top>
      <Left>1</Left>
      <AbsoluteLayoutFlags>XProportional</AbsoluteLayoutFlags>
      <Text Binding="OrderDate" />
    </TemplateItem>
    <TemplateItem Type="TemplateItemLabel">
      <Top>30</Top>
      <Left>0</Left>
      <Text Binding="ClientTitle" />
      <FontStyle>CollectionSubTitleFont</FontStyle>
    </TemplateItem>
    <TemplateItem Type="TemplateItemLabel">
      <Top>55</Top>
      <Left>0</Left>
      <Text Binding="TotalOrderCost" />
      <FontStyle>CollectionSubTitleFont</FontStyle>
    </TemplateItem>
    <TemplateItem Type="TemplateItemLabel">
      <Top>0.5</Top>
      <Left>1</Left>
      <Height>25</Height>
      <AbsoluteLayoutFlags>PositionProportional</AbsoluteLayoutFlags>
      <Visible Binding="IsPaid">False</Visible>
      <Text>Оплачен</Text>
      <ForeColor>StrongBlue</ForeColor>
      <FontStyle>CollectionStatusFont</FontStyle>
      <BackColor>VerySoftBlue</BackColor>
      <BorderCorner>7</BorderCorner>
      <TextAlignment Vertical="Center" Horizontal="Center" />
      <Padding Left="15" Top="0" Right="15" Bottom="0" />
    </TemplateItem>
  </CardTemplate>
</MyObject>

Обновление списка

Объекты типа Panel поддерживают тэг <RefreshCommand>, в котором описывается последовательность команд, необходимых для обновления данных. Вызывается эта последовательность смахиванием сверху вниз по панели.

Давайте создадим команду на обновления OrderPrimaryGetDataConnection:

TemplateMainForm.xml
<Command Name="OrderDataConnectionRefreshCommand" Type="DataConnectionRefreshCommand" Assembly="Commands">
  <DataConnections>
    <DataConnection Name="OrderPrimaryGetDataConnection" Packet="True" />
  </DataConnections>
</Command>

Создадим панель OrderCollectionPanel, которая будет родительским контейнером для объекта OrderCollectionView:

TemplateMainForm.xml
<MyObject Name="OrderCollectionPanel" Type="Panel" Assembly="BaseControls">
  <Top>0</Top>
  <Left>0</Left>
  <Height>
    <Object Name="ContentPanel">
      <Property Name="Height" />
    </Object>
  </Height>
  <Width>
    <Object Name="ContentPanel">
      <Property Name="Width" />
    </Object>
  </Width>
  <RefreshCommands>
    <Command Name="OrderDataConnectionRefreshCommand" />
  </RefreshCommands>
  
  <MyObject Name="OrderCollectionView" Type="CollectionView" Assembly="WorkflowForms">
    <!-- ... -->
  </MyObject>
  
</MyObject>

В тэге <RefreshCommand> новой панели указали команду OrderDataConnectionRefreshCommand.

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

Клик по карточке

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

Чтобы получить значение поля из строки источника данных, соответствующей выделенной карточке, будем использовать get-проперти SelectedItemValueByFieldName, которое принимает один параметр FieldName с именем этого поля. Например, получим значение из поля OrderId:

<Object Name="OrderCollectionView">
  <Property Name="SelectedItemValueByFieldName">
    <Parameters>
      <Parameter Name="FieldName">OrderId</Parameter>
    </Parameters>
  </Property>
</Object>

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

Создадим команду на открытие формы редактирования заказа и передадим в ней параметр со значением OrderId выбранного заказа:

TemplateMainForm.xml
<Command Name="OrderEditFormShowCommand" Type="FormShowCommand" Assembly="Commands">
  <Xml Type="Path">TemplateOrderEdit.xml</Xml>
  <Show Type="None" />
  <Parameters>
    <Parameter Name="OrderId">
      <Object Name="OrderCollectionView">
        <Property Name="SelectedItemValueByFieldName">
          <Parameters>
            <Parameter Name="FieldName">OrderId</Parameter>
          </Parameters>
        </Property>
      </Object>
    </Parameter>
  </Parameters>
</Command>

Создадим условие, в котором будем проверять значение get-проперти SelectedItemChanged у нашего списка:

TemplateMainForm.xml
<Condition Name="CollectionViewSelectedItemChangedEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <AlwaysChange Value="True" />
  <Items>
    <Item>
      <Object Name="OrderCollectionView">
        <Property Name="SelectedItemChanged" />
      </Object>
    </Item>
    <Item>True</Item>
  </Items>
</Condition>

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

TemplateMainForm.xml
<Execution>
  <ConditionExpression>
    <Condition Name="CollectionViewSelectedItemChangedEqualCondition" />
  </ConditionExpression>
  <Commands>
    <Command Name="OrderEditFormShowCommand" />
  </Commands>
</Execution>

Экран заказа

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

Основная информация

Самостоятельно создайте DataConnection для данных о заказе - OrderPrimaryGetDataConnection.

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

TemplateOrderEdit.xml
<FontStyle Name="Label15ItalicFont" Font="Segoe UI" Size="15" Italic="True" />
<FontStyle Name="Label16Font" Font="Segoe UI" Size="16" />

<FontStyle Name="CollectionTitleFont" Font="Arial" Size="16" />
<FontStyle Name="CollectionSubTitleFont" Font="Arial" Size="15" />
<FontStyle Name="CollectionStatusFont" Font="Arial" Size="14" />

Скорректируем заголовок экрана, изменив значение тэга <Text> объекта HeadTitleLabel:

<Text>
  <String>
    <Format>Заказ от {0}</Format>
    <Items>
      <Item>
        <DataTypeFormat Type="DateDataType" Format="dd MMM yyyy">
          <DataConnection SourceDataConnection="OrderPrimaryGetDataConnection">
            <Fields>
              <Field Name="OrderDate" />
            </Fields>
          </DataConnection>
        </DataTypeFormat>
      </Item>
    </Items>
  </String>
</Text>

Так как список позиций заказа может выходить за границы экрана, то для удобной работы в объект ContentPanel добавим тэг <VerticalScroll> со значением True.

Первым добавим на экран номер заказа, поместив поле с заголовком в панель:

TemplateOrderEdit.xml
<MyObject Name="OrderNumberPanel" Type="Panel" Assembly="BaseControls">
  <Top>10</Top>
  <Left>10</Left>
  <Height>
    <Calculate>
      <Expression>{0} + 15</Expression>
      <Items>
        <Item>
          <Object Name="OrderNumberValueLabel">
            <Property Name="Bottom" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Height>
  <Width>
    <Calculate>
      <Expression>{0} - {1}*2</Expression>
      <Items>
        <Item>
          <Object Name="ContentPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="OrderNumberPanel">
            <Property Name="Left" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Width>
  <BorderCorner>7</BorderCorner>
  <BackColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="AppThemeDarkEqualCondition" />
        </When>
        <Then>VeryDarkGray</Then>
      </Case>
      <Case>WhiteSmoke</Case>
    </Switch>
  </BackColor>


</MyObject>

Высоту панели сделали динамической и привязали к нижней границы объекта OrderNumberValueLabel.

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

Добавим в объект OrderNumberPanel описание двух Label, которые будут отображать наименование поля и данные:

TemplateOrderEdit.xml
<MyObject Name="OrderNumberTitleLabel" Type="Label" Assembly="BaseControls">
  <Top>10</Top>
  <Left>15</Left>
  <Width>110</Width>
  <Height>25</Height>
  <TextAlign>MiddleLeft</TextAlign>
  <FontStyle>Label15ItalicFont</FontStyle>
  <ForeColor>Gray</ForeColor>
  <Text>Номер заказа</Text>
</MyObject>

<MyObject Name="OrderNumberValueLabel" Type="Label" Assembly="BaseControls">
  <Top>
    <Object Name="OrderNumberTitleLabel">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Object Name="OrderNumberTitleLabel">
      <Property Name="Right" />
    </Object>
  </Left>
  <Width>
    <Calculate>
      <Expression>{0} - {1} - 40</Expression>
      <Items>
        <Item>
          <Object Name="OrderNumberPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="OrderNumberValueLabel">
            <Property Name="Left" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Width>
  <Height>25</Height>
  <TextAlign>MiddleRight</TextAlign>
  <FontStyle>Label16Font</FontStyle>
  <Text>
    <DataConnection SourceDataConnection="OrderPrimaryGetDataConnection">
      <Fields>
        <Field Name="OrderNumber" />
      </Fields>
    </DataConnection>
  </Text>
</MyObject>

Создадим блок с данными о клиенте. Блок будет представлен объектом Panel и копировать стиль панели OrderNumberPanel, но теперь он будет иметь заголовок в виде объекта Label:

TemplateOrderEdit.xml
<MyObject Name="ClientPartTitleLabel" Type="Label" Assembly="BaseControls">
  <Top>
    <Calculate>
      <Expression>{0} + 20</Expression>
      <Items>
        <Item>
          <Object Name="OrderNumberPanel">
            <Property Name="Bottom" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Top>
  <Left>35</Left>
  <Height>25</Height>
  <Width>200</Width>
  <ForeColor>Gray</ForeColor>
  <Text>КЛИЕНТ</Text>
</MyObject>

<MyObject Name="ClientPanel" Type="Panel" Assembly="BaseControls">
  <Top>
    <Object Name="ClientPartTitleLabel">
      <Property Name="Bottom" />
    </Object>
  </Top>
  <Left>
    <Object Name="OrderNumberPanel">
      <Property Name="Left" />
    </Object>
  </Left>
  <Height>
    <Calculate>
      <Expression>{0} + 15</Expression>
      <Items>
        <Item>
          <Object Name="EmailValueLabel">
            <Property Name="Bottom" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Height>
  <Width>
    <Object Name="OrderNumberPanel">
      <Property Name="Width" />
    </Object>
  </Width>
  <BorderCorner>
    <Object Name="OrderNumberPanel">
      <Property Name="BorderCorner" />
    </Object>
  </BorderCorner>
  <BackColor>
    <Object Name="OrderNumberPanel">
      <Property Name="BackColor" />
    </Object>
  </BackColor>
    
</MyObject>

Высоту панели сразу привяжем к нижней границы объекта EmailValueLabel.

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

TemplateOrderEdit.xml
<MyObject Name="ClientTitleLabel" Type="Label" Assembly="BaseControls">
  <Top>10</Top>
  <Left>15</Left>
  <Width>110</Width>
  <Height>25</Height>
  <TextAlign>MiddleLeft</TextAlign>
  <FontStyle>Label15ItalicFont</FontStyle>
  <ForeColor>Gray</ForeColor>
  <Text>Клиент</Text>
</MyObject>

<MyObject Name="ClientValueLabel" Type="Label" Assembly="BaseControls">
  <Top>
    <Object Name="ClientTitleLabel">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Object Name="ClientTitleLabel">
      <Property Name="Right" />
    </Object>
  </Left>
  <Width>
    <Calculate>
      <Expression>{0} - {1} - 40</Expression>
      <Items>
        <Item>
          <Object Name="ClientPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="ClientValueLabel">
            <Property Name="Left" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Width>
  <Height>25</Height>
  <TextAlign>MiddleRight</TextAlign>
  <FontStyle>Label16Font</FontStyle>
  <Text>
    <DataConnection SourceDataConnection="OrderPrimaryGetDataConnection">
      <Fields>
        <Field Name="ClientTitle" />
      </Fields>
    </DataConnection>
  </Text>
</MyObject>

Так как список клиентов может быть очень большим, то использование ComboBox затруднит поиск нужного. Поэтому для выбора клиента будем использовать отдельный экран, на котором список клиентов будет занимать большую часть пространства. В таком случае, для отображения имени клиента в заказе будет достаточно объекта типа Label, а справа от него добавим кнопку для перехода на форму выбора клиента:

TemplateOrderEdit.xml
<MyObject Name="ClientEditButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Calculate>
      <Expression>{0} - 2</Expression>
      <Items>
        <Item>
          <Object Name="ClientValueLabel">
            <Property Name="Top" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Top>
  <Left>
    <Calculate>
      <Expression>{0} + 2</Expression>
      <Items>
        <Item>
          <Object Name="ClientValueLabel">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Height>30</Height>
  <Width>30</Width>
  <BackColor>
    <Switch>
      <Case>
        <When>
          <Condition Name="AppThemeDarkEqualCondition" />
        </When>
        <Then>VeryDarkGray</Then>
      </Case>
      <Case>WhiteSmoke</Case>
    </Switch>
  </BackColor>
  <BackgroundImage>Images\chevron-right.png</BackgroundImage>
  <BackgroundImageLayout>Zoom</BackgroundImageLayout>
</MyObject>

Для нее нам понадобится изображение (скачайте его и скопируйте в папку \MobileForms\Images):

Самостоятельно добавьте поля для города, телефона и email клиента. Расстояние между заголовками полей делайте в 10 единиц. В результате должна получиться форма вида:

На скриншоте можете увидеть объекты-заглушки с текстом "Не указан". Так как телефон и email являются необязательными полями в карточке клиента, и чтобы на экране не было пустых мест, будем использовать такие объекты-заглушки. Самостоятельно создайте необходимые объекты и условия отображения основного текста и заглушки. Шрифт у объектов-заглушек будет Label16Font, как и у основных объектов, а цвет текста будет Gray.

Теперь перейдем к созданию блока оплат. Здесь по аналогии с предыдущим блоком создайте заголовок блока PaymentPartTitleLabel и панель PaymentPanel с необходимыми полями:

Статус "Оплачен" будем отображать вместо значения задолженности:

Плашка статуса - это объект Panel со скругленными углами и цветным фоном, внутри которого размещен объект Label:

TemplateOrderEdit.xml
<MyObject Name="IsPaidPanel" Type="Panel" Assembly="BaseControls">
  <Top>
    <Object Name="TotalDebtTitleLabel">
      <Property Name="Top" />
    </Object>
  </Top>
  <Right>
    <Calculate>
      <Expression>{0}</Expression>
      <Items>
        <Item>
          <Object Name="TotalDebtValueLabel">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Right>
  <Height>25</Height>
  <Width>100</Width>
  <BorderCorner>7</BorderCorner>
  <BackColor>VerySoftBlue</BackColor>
  <Visible>
    <Condition Name="OrderIsPaidGreaterCondition" />
  </Visible>

  <MyObject Name="IsPaidLabel" Type="Label" Assembly="BaseControls">
    <Top>0</Top>
    <Left>0</Left>
    <Height>25</Height>
    <Width>100</Width>
    <TextAlign>MiddleCenter</TextAlign>
    <Text>Оплачен</Text>
    <FontStyle>CollectionStatusFont</FontStyle>
    <ForeColor>StrongBlue</ForeColor>
  </MyObject>
</MyObject>

Позиции заказа

Создайте DataConnection для списка позиций заказа (OrderPositionPrimaryGetDataConnection), который будет получать данные из SQL-запроса:

Template.xml
<SqlQuery Name="AppOrderPositionByOrderIdSelectSqlQuery">
  <Text>
    SELECT
      OP.order_position_id AS "OrderPositionId",
      OP.material_id AS "MaterialId",
      material.title AS "MaterialTitle",
      material_category.title AS "MaterialCategoryTitle",
      unit.short_title AS "UnitShortTitle",
      OP.quantity AS "Quantity",
      OP.quantity || ' ' || unit.short_title AS "QuantityString",
      trim(to_char(OP.unit_price, '999G999G999D00')) || ' руб./' || unit.short_title AS "UnitPrice",
      trim(to_char(OP.unit_price * OP.quantity, '999G999G999D00')) || ' руб.'  AS "TotalPrice"
    FROM
      template.order_position OP
      LEFT JOIN template.material USING(material_id)
      LEFT JOIN template.material_category USING(material_category_id)
      LEFT JOIN template.unit USING(unit_id)
    WHERE
      OP.order_id = {OrderId}
    ORDER BY OP.order_position_id;
  </Text>
</SqlQuery>

В запросе возвращаем два поля с количеством товара: Quantity и QuantityString - первое пригодится в следующем уроке, когда будем реализовывать экран изменения позиций заказа, а второе будем отображать в карточке списка позиций.

Для списка позиций заказа не будем использовать панель - достаточно задать заголовок блока и определить объект CollectionView.

Самостоятельно создайте список позиций заказа с именем OrderPositionCollectionView. Должна получиться форма вида:

Итоги

На уроке мы рассмотрели .

Ответы

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

Архив не содержит xml-файлы форм десктопного приложения.

Last updated