Урок 4. Редактирование списка
На прошлом уроке создали экран редактирования заказа, но реализацию возможности изменять клиента и редактировать позиции заказа оставил на этот урок.
Скачайте архив с изображениями и разархивируйте его в папку проекта \Template\Projects\1. Template\MobileForms\Images.
Редактирование клиента
Так как список клиентов может быть большим и неудобным для использования ComboBox, то договорились для выбора клиента использовать отдельный экран. В прошлом уроке на экран редактирования заказа справа от поля с именем клиента добавили кнопку, которую планировали использовать для перехода на форму со списком клиентов. Самостоятельно создайте пустую форму списка клиентов и команду открытия этой формы, которая будет вызываться по ранее созданной кнопке:
<Command Name="ClientSelectFormShowCommand" Type="FormShowCommand" Assembly="Commands">
<Xml Type="Path">TemplateClientSelect.xml</Xml>
<Show Type="None" />
<Parameters>
<Parameter Name="ClientId">
<DataConnection SourceDataConnection="OrderPrimaryGetDataConnection">
<Fields>
<Field Name="ClientId" />
</Fields>
</DataConnection>
</Parameter>
</Parameters>
</Command>
Для удобства вызова списка клиентов, новую команду так же можно вызывать при клике по имени пользователя. Реализуйте эту возможность самостоятельно.
На форму списка клиентов передаем значение ClientId текущего клиента в заказе, полученного из базы данных. Помним, что запрос на получение списка должен возвращать все неархивные записи и запись, на которую ссылается редактируемый заказ.
Строка поиска
Чтобы пользователю было удобно работать с потенциально большим списком, добавим возможность поиска клиента по имени.
Строка поиска будет представлена объектом типа Panel с именем SearchPanel, содержащим элементы:
SearchButton - хоть и является объектом типа Button, но это просто изображение лупы, не выполняющее никакой команды
SearchTextBox - текстовое поле для ввода строки поиска
ClearSearchButton - кнопка с изображением повернутого крестика для очистки строки поиска
Объект SearchPanel будет иметь настройки BorderCorner и BackColor аналогичные тем, что делали для панелей блоков на экране заказа. Высота панели 40 единиц.
Объект SearchButton будет иметь код:
<MyObject Name="SearchButton" Type="Button" Assembly="BaseControls">
<Top>2</Top>
<Left>5</Left>
<Height>35</Height>
<Width>35</Width>
<BackColor>
<Object Name="SearchPanel">
<Property Name="BackColor" />
</Object>
</BackColor>
<BackgroundImage>Images\magnify.png</BackgroundImage>
<BackgroundImageLayout>Zoom</BackgroundImageLayout>
</MyObject>
Самостоятельно создайте объект SearchTextBox, который будет справа от SearchButton без отступов. По высоте текстовое поле должно совпадать с высотой панели, следовательно отступ сверху будет нулевым. Ширина текстового поля должна учитывать размер кнопки ClearSearchButton и оставлять отступ справа в 10 единиц. Для объекта SearchTextBox текст-подсказки будет "Поиск". А его цвет зависит от оформления системы: для темной темы используется DarkGray, а для светлой темы - LightGray. Цвет основного текста (тэг <ForeColor>
) тоже зависит от выбранного оформления системы: для темной темы используется WhiteSmoke, а для светлой темы - MostlyBlack. Значение тэга <BackColor>
можно тянуть из аналогичного свойства объекта SearchPanel. В тэге <FontStyle>
укажем значение TextFont. По умолчанию TextBox имеет рамку, чтобы ее убрать используем тэг <Multiline>
со значением True.
Кнопка ClearSearchButton для сброса значения объекта SearchTextBox будет иметь вид:
<MyObject Name="ClearSearchButton" Type="Button" Assembly="BaseControls">
<Top>5</Top>
<Left>
<Calculate>
<Expression>{0} + 2</Expression>
<Items>
<Item>
<Object Name="SearchTextBox">
<Property Name="Right" />
</Object>
</Item>
</Items>
</Calculate>
</Left>
<Height>30</Height>
<Width>30</Width>
<BackColor>
<Object Name="SearchPanel">
<Property Name="BackColor" />
</Object>
</BackColor>
<BackgroundImage>Images\close.png</BackgroundImage>
<BackgroundImageLayout>Zoom</BackgroundImageLayout>
<Commands>
<Command Name="ResetSearchStringValueSetCommand" />
</Commands>
</MyObject>
Самостоятельно создайте команду ResetSearchStringValueSetCommand.
Запустите приложение и проверьте отображение строки поиска на экране выбора клиента.


Отлично!
Самостоятельно добавьте на форму список с клиентами ClientCollectionView. В качестве источника данных используйте SecondaryGetDataConnection, в котором должны быть отфильтрованные по строке поиска данный из PrimaryGetDataConnection.
Запрос на получение списка клиентов должен содержать поля:
В результате у вас должна получиться форма вида:


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


Выбор клиента
Добавим на форму параметры, в которых будем возвращать на родительскую форму данные выбранного клиента:
<Parameter Name="ClientTitle" />
<Parameter Name="CityTitle" />
<Parameter Name="ClientEmail" />
<Parameter Name="ClientPhone" />
Добавьте команды типа ValueSetCommand, в которых всем параметрам формы, включая ClientId, присваиваются данные выбранного клиента.
Все команды объединим в одну последовательность:
<Command Name="SetParametersSequentialCommand" Type="SequentialCommand" Assembly="Commands">
<Commands>
<Command Name="ClientIdValueSetCommand" />
<Command Name="ClientTitleValueSetCommand" />
<Command Name="CityTitleValueSetCommand" />
<Command Name="ClientEmailValueSetCommand" />
<Command Name="ClientPhoneValueSetCommand" />
</Commands>
</Command>
Добавим условие на изменение выбранной записи, в котором будем проверять значение get-проперти SelectedItemChanged:
<Condition Name="CollectionViewSelectedItemChangedEqualCondition" Type="EqualCondition" Assembly="Conditions">
<AlwaysChange Value="True" />
<Items>
<Item>
<Object Name="ClientCollectionView">
<Property Name="SelectedItemChanged" />
</Object>
</Item>
<Item>True</Item>
</Items>
</Condition>
На это условие создадим Execution, в котором будем сохранять данные в параметры, ставить флаг изменений и уходить с экрана выбора клиента:
<Execution>
<ConditionExpression>
<Condition Name="CollectionViewSelectedItemChangedEqualCondition" />
</ConditionExpression>
<Commands>
<Command Name="SetParametersSequentialCommand" />
<Command Name="UpdatedTrueValueSetCommand" />
<Command Name="FormCloseCommand" />
</Commands>
</Execution>
Как видите на форме используем паттерн Updated - добавьте в файл параметр Updated и создайте команду для его обновления.
Замена клиента в заказе
Вернемся в файл описания карточки заказа и реализуем обновления данных о клиенте в OrderPrimaryGetDataConnection. Самостоятельно создайте команду типа ValueSetCommand, в которой заменяйте значения в полях DataConnection соответствующих возвращаемым параметрам. Реализуйте Execution, в котором будет вызываться эта команда, используя паттерн Updated.
Запустите приложение и проверьте функциональность смены клиента в заказе.
Дополнительно: на кнопку смены клиента наложите ограничение, если заказ оплачен.
Редактирование позиций заказа
Начнем с реализации возможности добавление позиций в заказ и изменение кол-ва товара в позиции. Для этого будем использовать отдельную форму со списком ТМЦ. А затем добавим удаление позиций с помощью свайпа в карточке заказа.
Подготовка
Первым делом создадим ConvertDataConnection, который будет преобразовывать OrderPositionPrimaryGetDataConnection:
<DataConnection Name="OrderPositionConvertDataConnection" Type="ConvertDataConnection" Assembly="WorkflowServer">
<SourceDataConnection Name="OrderPositionPrimaryGetDataConnection" />
<Fields>
<Field Name="OrderPositionId" />
<Field Name="MaterialId" />
<Field Name="MaterialTitle" />
<Field Name="MaterialCategoryTitle" />
<Field Name="UnitPrice" />
<Field Name="UnitPriceString" />
<Field Name="TotalPrice" />
<Field Name="Quantity" />
<Field Name="QuantityString" />
<Field Name="UnitShortTitle" />
<Field Name="ID" Type="Value">
<Object Name="CounterVariable" />
</Field>
<Field Name="Updated" Type="Value" DataType="BooleanDataType">False</Field>
<Field Name="Deleted" Type="Value" DataType="BooleanDataType">False</Field>
<Field Name="IsNew" Type="Value" DataType="BooleanDataType">False</Field>
</Fields>
</DataConnection>
Здесь мы добавляем поле ID, которое будет временным идентификатором, который будем передавать на форму со списком товаров, чтобы правильно выставить флаги Added, Upated и Deleted. О них подробнее позже. Так же добавили поле IsNew, флаг которым будем помечать позиции заказа не сохраненные в базе данных.
В качестве значения нового поля указали объект CounterVariable, который является переменной-счетчиком и каждый раз при обращении увеличивает текущее значение на единицу и возвращает его.
<MyObject Name="CounterVariable" Type="CounterVariable" Assembly="WorkflowServer" ChangeForm="False">
<Value />
</MyObject>
Укажем OrderPositionConvertDataConnection в качестве источника данных для объекта OrderPositionCollectionView.
Создадим форму, которую будем использовать для выбора товаров из каталога. Назовем ее TemplateMaterialList.xml.
Создадим команду для открытия новой формы:
<Command Name="MaterialListFormShowCommand" Type="FormShowCommand" Assembly="Commands">
<Xml Type="Path">TemplateMaterialList.xml</Xml>
<Show Type="None" />
<Parameters>
<Parameter Name="MaterialArray">
<DataConnection SourceDataConnection="OrderPositionConvertDataConnection">
<Fields>
<Field Name="ID" />
<Field Name="MaterialId" />
<Field Name="Quantity" />
<Field Name="IsNew" />
</Fields>
</DataConnection>
</Parameter>
</Parameters>
</Command>
На форму каталога передаем параметр MaterialArray с массивом добавленных товаров в заказ.
На экране редактирования заказа добавьте кнопку Изменить, укажите для нее команду MaterialListFormShowCommand. Кнопку разместите справа над списком позиций заказов:
Список товаров
Перейдем в файл новой формы и в него добавим строку поиска, как делали на форме выбора клиента.
С родительской формы передаем параметр MaterialArray, добавим его на форму, а чтобы было удобно работать с передаваемым массивом данных создадим ArrayGetDataConnection:
<DataConnection Name="MaterialArrayGetDataConnection" Type="ArrayGetDataConnection" Assembly="DataConnections">
<Source>
<Parameter Name="MaterialArray" />
</Source>
<Fields>
<Field Name="ID" />
<Field Name="MaterialId" />
<Field Name="Quantity" DataType="IntegerDataType" />
</Fields>
</DataConnection>
Создайте самостоятельно MaterialPrimaryGetDataConnection, который будет получать данные из запроса:
<SqlQuery Name="AppMaterialSelectSqlQuery">
<Text>
SELECT
M.material_id AS "MaterialId",
M.title AS "Title",
M.material_category_id AS "MaterialCategoryId",
MC.title AS "MaterialCategoryTitle",
U.short_title AS "UnitShortTitle",
M.unit_price AS "UnitPrice",
trim(to_char(M.unit_price, '999G999G999D00')) || ' руб./' || U.short_title AS "UnitPriceString",
M.material_id != ALL({MaterialId}::bigint[]) AS "NotUse",
M.material_id = ANY({MaterialId}::bigint[]) AS "Selected"
FROM
template.material M
LEFT JOIN template.material_category MC USING(material_category_id)
LEFT JOIN template.unit U USING(unit_id)
WHERE
NOT M.archive OR M.material_id = ANY({MaterialId}::bigint[])
ORDER BY M.title;
</Text>
</SqlQuery>
В качестве параметра в запрос передаем массив MaterialId, полученных с родительской формы.
Здесь так же возвращаем два поля с ценой за единицу товара: UnitPrice и UnitPriceString - значение из первого будем возвращать на родительскую форму и использовать для расчета итоговой стоимости, а второе для отображения в карточке товара.
Так же возвращаем два флага:
NotUse - true, если в заказе нет позиции с этим товаром, по этому флагу будет отображаться кнопка "Добавить" в карточке товара,
Selected - true, если в заказе есть позиция с этим товаром, по этому флагу будет отображаться количество товара и кнопки изменения.
Обернем загружающее соединение в ConvertDataConnection и добавим новые поля:
<DataConnection Name="MaterialConvertDataConnection" Type="ConvertDataConnection" Assembly="WorkflowServer">
<SourceDataConnection Name="MaterialPrimaryGetDataConnection" />
<Fields>
<Field Name="MaterialId" />
<Field Name="Title" />
<Field Name="MaterialCategoryId" />
<Field Name="MaterialCategoryTitle" />
<Field Name="UnitShortTitle" />
<Field Name="UnitPrice" />
<Field Name="UnitPriceString" />
<Field Name="Selected" />
<Field Name="NotUse" />
<Field Name="TotalPrice" />
<Field Name="Quantity" Type="Substitution" Field="MaterialId">
<DataConnection SourceDataConnection="MaterialArrayGetDataConnection">
<Fields>
<Field Name="MaterialId" />
<Field Name="Quantity" />
</Fields>
</DataConnection>
</Field>
<Field Name="ID" Type="Substitution" Field="MaterialId">
<DataConnection SourceDataConnection="MaterialArrayGetDataConnection">
<Fields>
<Field Name="MaterialId" />
<Field Name="ID" />
</Fields>
</DataConnection>
</Field>
<Field Name="Added" Type="Value" DataType="BooleanDataType">False</Field>
<Field Name="Updated" Type="Value" DataType="BooleanDataType">False</Field>
<Field Name="Deleted" Type="Value" DataType="BooleanDataType">False</Field>
</Fields>
</DataConnection>
Поле TotalPrice необходимо для хранения итоговой суммы по выбранному товару.
В полях Quantity и ID подставляем соответствующие значения из массива полученного от родительской формы.
Поля Added, Updated и Deleted необходимы, чтобы при передачи на родительскую форму различать новые товары, которые нужно добавить через set-проперти AddRows, позиции в которых нужно обновить количество товара, и позиции подлежащие удалению из заказа.
Создайте MaterialCollectionView для отображения списка товаров. Добавьте в шаблон карточки элемента списка описание полей для наименования товара, его категории и цены за единицу.
Сразу создадим условие, в котором будем проверять наличие значения в поле ID:
<Condition Name="SelectedIDIsNullOrEmptyCondition" Type="IsNullOrEmptyCondition" Assembly="Conditions">
<AlwaysChange Value="True" />
<Items>
<Item>
<Object Name="MaterialCollectionView">
<Property Name="SelectedItemValueByFieldName">
<Parameters>
<Parameter Name="FieldName">ID</Parameter>
</Parameters>
</Property>
</Object>
</Item>
</Items>
<DataType Type="IntegerDataType" />
</Condition>
Отлично, теперь можем реализовывать логику изменения позиций в заказе.
Добавление товара
В шаблоне карточки списка товаров MaterialCollectionView опишем кнопку Добавить:
<TemplateItem Type="TemplateItemButton">
<Top>50</Top>
<Left>1</Left>
<Width>125</Width>
<Height>30</Height>
<AbsoluteLayoutFlags>XProportional</AbsoluteLayoutFlags>
<Text>Добавить</Text>
<FontStyle>CollectionSubTitleFont</FontStyle>
<ForeColor>WhiteSmoke</ForeColor>
<BackColor>ColorPrimary</BackColor>
<BorderCorner>7</BorderCorner>
<Commands>
<Command Name="MaterialAddValueSetCommand" />
</Commands>
<Visible Binding="NotUse" />
</TemplateItem>
Создадим команду MaterialAddValueSetCommand типа ValueSetCommand, используемую на кнопке:
<Command Name="MaterialAddValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<DataConnection Name="MaterialConvertDataConnection">
<Property Name="UpdateRow">
<Parameters>
<Parameter Name="RowIndex">
<DataConnection SourceDataConnection="MaterialConvertDataConnection">
<Property Name="RowIndexOf">
<Parameters>
<Parameter Name="ColumnNames">
<Structure Type="List">
<Item>MaterialId</Item>
</Structure>
</Parameter>
<Parameter Name="Values">
<Structure Type="List">
<Item>
<Object Name="MaterialCollectionView">
<Property Name="SelectedItemValueByFieldName">
<Parameters>
<Parameter Name="FieldName">MaterialId</Parameter>
</Parameters>
</Property>
</Object>
</Item>
</Structure>
</Parameter>
</Parameters>
</Property>
</DataConnection>
</Parameter>
<Parameter Name="ColumnNames">
<Structure Type="List">
<Item>NotUse</Item>
<Item>Selected</Item>
<Item>Quantity</Item>
<Item>TotalPrice</Item>
<Item>Added</Item>
<Item>Updated</Item>
<Item>Deleted</Item>
</Structure>
</Parameter>
<Parameter Name="Values">
<Structure Type="List">
<Item>False</Item>
<Item>True</Item>
<Item>1</Item>
<Item>
<Calculate>
<Expression>{0} * 1</Expression>
<Items>
<Item>
<Object Name="MaterialCollectionView">
<Property Name="SelectedItemValueByFieldName">
<Parameters>
<Parameter Name="FieldName">UnitPrice</Parameter>
</Parameters>
</Property>
</Object>
</Item>
</Items>
</Calculate>
</Item>
<Item>
<Condition Name="SelectedIDIsNullOrEmptyCondition" />
</Item>
<Item>
<Not>
<Condition Name="SelectedIDIsNullOrEmptyCondition" />
</Not>
</Item>
<Item>False</Item>
</Structure>
</Parameter>
</Parameters>
</Property>
</DataConnection>
</Command>
В команде находим индекс строки в MaterialConvertDataConnection по значению MaterialId элемента, по кнопке которого сделали тап, и в найденной строке меняем значения флагов и ставим количество равное единице. В результате кнопка исчезнет из карточки и вместо нее должны появиться элементы управления количеством товара в заказе.
Так же изменяем значения в полях Added, Updated и Deleted. Новые значения для Added и Updated зависят от наличия временного идентификатора на тот случай, если сначала удалили товар, и сразу нажали на кнопку Добавить. Если у товара есть временный ID, то мы должны рассматривать изменение существующего, а не добавление нового.
Управление количеством
Первой опишем кнопку уменьшения количества:
<ItemElement Type="TemplateItemButton">
<Top>50</Top>
<Left>0.72</Left>
<Width>30</Width>
<Height>30</Height>
<AbsoluteLayoutFlags>XProportional</AbsoluteLayoutFlags>
<BackColor>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>DarkGray70</Then>
</Case>
<Case>VeryLightGray</Case>
</Switch>
</BackColor>
<BorderCorner>7</BorderCorner>
<ImageSource>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>Images\minus-light.png</Then>
</Case>
<Case>Images\minus-dark.png</Case>
</Switch>
</ImageSource>
<Commands>
<If>
<When>
<Condition Name="QuantityOfSelectedMaterialGreater1Condition" />
</When>
<Then>
<Command Name="MaterialDecrementQuantityValueSetCommand" />
</Then>
<Else>
<Command Name="MaterialDeleteValueSetCommand" />
</Else>
</If>
</Commands>
<Visible Binding="Selected" />
</ItemElement>
В условие QuantityOfSelectedMaterialGreater1Condition проверяем количество на больше единицы, чтобы определить, какую команду следует выполнить. Если количество больше единицы, то будем его уменьшать на единицу, а если равно единицы, то меняем значения флагов NotUse и Selected на противоположные, а количество сбрасываем на 0.
Самостоятельно напишите условие и обе команды. Не забудьте менять значения в полях Added, Updated и Deleted, учитывая при этом временный ID. Если у товара нет временного идентификатора, то можно просто сбросить количество и не помечать его на удаление из карточки заказа. При уменьшении количества товара важно правильно
Текущее количество товара будем отображать в объекте:
<TemplateItem Type="TemplateItemLabel">
<Top>50</Top>
<Left>0.87</Left>
<Width>45</Width>
<Height>30</Height>
<AbsoluteLayoutFlags>XProportional</AbsoluteLayoutFlags>
<FontStyle>CollectionTitleFont</FontStyle>
<ForeColor>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>WhiteSmoke</Then>
</Case>
<Case>VeryDarkGray</Case>
</Switch>
</ForeColor>
<BorderCorner>7</BorderCorner>
<BackColor>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>DarkGray70</Then>
</Case>
<Case>VeryLightGray</Case>
</Switch>
</BackColor>
<TextAlignment Vertical="Center" Horizontal="Center" />
<Padding Left="5" Top="0" Right="5" Bottom="0" />
<Text Binding="Quantity" />
<Visible Binding="Selected" />
</TemplateItem>
Кнопка увеличения количества товара, будет выполнять только одну команду:
<ItemElement Type="TemplateItemButton">
<Top>50</Top>
<Left>0.99</Left>
<Width>30</Width>
<Height>30</Height>
<AbsoluteLayoutFlags>XProportional</AbsoluteLayoutFlags>
<BackColor>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>DarkGray70</Then>
</Case>
<Case>VeryLightGray</Case>
</Switch>
</BackColor>
<BorderCorner>7</BorderCorner>
<ImageSource>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>Images\plus-light.png</Then>
</Case>
<Case>Images\plus-dark.png</Case>
</Switch>
</ImageSource>
<Commands>
<Command Name="MaterialIncrementQuantityValueSetCommand" />
</Commands>
<Visible Binding="Selected" />
</ItemElement>
Самостоятельно создайте команду MaterialIncrementQuantityValueSetCommand.
Добавим в файл формы описание цвета, который используется в тэгах <BackColor>
созданных элементов:
<Color Name="DarkGray70" Red="70" Green="70" Blue="70" Alpha="255" />
Запустим приложение, чтобы проверить отображение и работу новых элементов:
Отлично! Теперь можем реализовать логику передачи данных на родительский экран, где будем обновлять список позиций заказа.
Передача изменений
В заголовок формы (HeadPanel) добавим кнопку Сохранить:
<MyObject Name="SaveButton" Type="Button" Assembly="BaseControls">
<Top>0</Top>
<Right>
<Calculate>
<Expression>{0} - 15</Expression>
<Items>
<Item>
<Object Name="HeadPanel">
<Property Name="Width" />
</Object>
</Item>
</Items>
</Calculate>
</Right>
<Height>
<Object Name="HeadPanel">
<Property Name="Height" />
</Object>
</Height>
<Width>100</Width>
<TextAlign>MiddleCenter</TextAlign>
<Text>Сохранить</Text>
<FontStyle>ButtonFont</FontStyle>
<ForeColor>ColorPrimary</ForeColor>
<BackColor>
<Object Name="HeadPanel">
<Property Name="BackColor" />
</Object>
</BackColor>
<Commands>
<Command Name="SaveSequentialCommand" />
<Command Name="FormCloseCommand" />
</Commands>
</MyObject>
Создайте самостоятельно команду SaveSequentialCommand типа SequentialCommand, оставив ее пока пустой.
Добавим параметры формы, в которых будем возвращать данные по товарам:
<Parameter Name="MaterialArrayToAdd" />
<Parameter Name="MaterialArrayToEdit" />
<Parameter Name="MaterialArrayToDelete" />
Создадим команды на запись данных в параметры.
Первой будет команда для параметра MaterialArrayToAdd. В нем будем получать из MaterialConvertDataConnection все записи, у которых в поле Added стоит значение True:
<Command Name="MaterialArrayToAddValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Parameter Name="MaterialArrayToAdd">
<Array>
<Source>
<DataConnection SourceDataConnection="MaterialConvertDataConnection">
<Fields>
<Field Name="MaterialId" />
<Field Name="Title" />
<Field Name="MaterialCategoryTitle" />
<Field Name="UnitShortTitle" />
<Field Name="UnitPrice" />
<Field Name="Quantity" />
<Field Name="TotalPrice" />
<Field Name="Added" />
</Fields>
</DataConnection>
</Source>
<Filter>
<Index Value="7" />
<DataType Type="BooleanDataType" />
<Value>True</Value>
</Filter>
<Select>
<Items>
<Item Type="Index">0</Item>
<Item Type="Index">1</Item>
<Item Type="Index">2</Item>
<Item Type="Index">3</Item>
<Item Type="Index">4</Item>
<Item Type="Index">5</Item>
<Item Type="Index">6</Item>
</Items>
</Select>
</Array>
</Parameter>
</Command>
В параметр MaterialArrayToEdit будем добавлять те записи, у которых в поле Updated стоит значение True и дополнительно будем проверять значение в поле Added, чтобы оно было равно False:
<Command Name="MaterialArrayToEditValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Parameter Name="MaterialArrayToEdit">
<Array>
<Source>
<DataConnection SourceDataConnection="MaterialConvertDataConnection">
<Fields>
<Field Name="ID" />
<Field Name="MaterialId" />
<Field Name="Title" />
<Field Name="MaterialCategoryTitle" />
<Field Name="UnitShortTitle" />
<Field Name="UnitPrice" />
<Field Name="Quantity" />
<Field Name="TotalPrice" />
<Field Name="Added" />
<Field Name="Updated" />
</Fields>
</DataConnection>
</Source>
<Filter Type="Nested">
<And>
<Filter>
<Index Value="8" />
<DataType Type="BooleanDataType" />
<Value>False</Value>
</Filter>
<Filter>
<Index Value="9" />
<DataType Type="BooleanDataType" />
<Value>True</Value>
</Filter>
</And>
</Filter>
<Select>
<Items>
<Item Type="Index">0</Item>
<Item Type="Index">1</Item>
<Item Type="Index">2</Item>
<Item Type="Index">3</Item>
<Item Type="Index">4</Item>
<Item Type="Index">5</Item>
<Item Type="Index">6</Item>
<Item Type="Index">7</Item>
</Items>
</Select>
</Array>
</Parameter>
</Command>
В параметре храним все поля выбранных записей, т.к. информация о ТМЦ могла быть изменена в базе данных.
В параметре MaterialArrayToDelete будем передавать массив временных идентификаторов товаром, помеченных на удаление:
<Command Name="MaterialArrayToDeleteValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Parameter Name="MaterialArrayToDelete">
<Array>
<Source>
<DataConnection SourceDataConnection="MaterialConvertDataConnection">
<Fields>
<Field Name="ID" />
<Field Name="Deleted" />
</Fields>
</DataConnection>
</Source>
<Filter>
<Index Value="1" />
<DataType Type="BooleanDataType" />
<Value>True</Value>
</Filter>
<Select>
<Items>
<Item Type="Index">0</Item>
</Items>
</Select>
</Array>
</Parameter>
</Command>
В карточке заказа будем определять, какие позиции удалить только на форме, а какие пометить для удаления в базе данных.
Объединим все три команды в ранее созданную команду SaveSequentialCommand:
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
<Commands>
<Command Name="MaterialArrayToAddValueSetCommand" />
<Command Name="MaterialArrayToEditValueSetCommand" />
<Command Name="MaterialArrayToDeleteValueSetCommand" />
<Command Name="UpdatedTrueValueSetCommand" />
</Commands>
</Command>
Не забываем про паттерн Updated, чтобы на родительском экране корректно отработал Execution.
Внесение изменений
Вернемся на родительскую форму чтобы добавить логику обработки полученных значений и внесения изменений в список позиций заказа.
Первым делом создайте условие MaterialListFormUpdatedTrueEqualCondition для проверки возвращаемого параметра Updated и Execution, который будет выполняться по этому условию.
Далее будет описывать команды на внесение изменений в OrderPositionConvertDataConnection и добавлять их в созданный Execution.
Команда на добавление позиций в заказ:
<Command Name="OrderPositionAddCommand" Type="ValueSetCommand" Assembly="Commands">
<DataConnection Name="OrderPositionConvertDataConnection">
<Property Name="AddRows">
<Parameters>
<Parameter Name="ColumnNames">
<Structure Type="List">
<Item>ID</Item>
<Item>MaterialId</Item>
<Item>MaterialTitle</Item>
<Item>MaterialCategoryTitle</Item>
<Item>UnitShortTitle</Item>
<Item>UnitPrice</Item>
<Item>UnitPriceString</Item>
<Item>Quantity</Item>
<Item>TotalPrice</Item>
<Item>QuantityString</Item>
<Item>Updated</Item>
<Item>Deleted</Item>
<Item>IsNew</Item>
</Structure>
</Parameter>
<Parameter Name="Values">
<Array>
<Source>
<Command Name="MaterialListFormShowCommand" Parameter="MaterialArrayToAdd" />
</Source>
<Select>
<Items>
<Item Type="Value">
<Object Name="CounterVariable" />
</Item>
<Item Type="Index">0</Item><!-- MaterialId -->
<Item Type="Index">1</Item><!-- Title -->
<Item Type="Index">2</Item><!-- MaterialCategoryTitle -->
<Item Type="Index">3</Item><!-- UnitShortTitle -->
<Item Type="Index">4</Item><!-- UnitPrice -->
<Item Type="Format">{4} руб./{3}</Item><!-- UnitPriceString -->
<Item Type="Index">5</Item><!-- Quantity -->
<Item Type="Format">{6} руб.</Item><!--TotalPrice-->
<Item Type="Format">{5} {3}</Item><!--QuantityString-->
<Item Type="Value">False</Item>
<Item Type="Value">False</Item>
<Item Type="Value">True</Item>
</Items>
</Select>
</Array>
</Parameter>
</Parameters>
</Property>
</DataConnection>
</Command>
Здесь разбираем данные из параметра MaterialArrayToAdd с помощью <Array>
и добавляем новые строки в OrderPositionConvertDataConnection. Новые позиции помечаем в поле IsNew значением True.
Команда на изменение позиций заказа:
<Command Name="OrderPositionToEditCommand" Type="ValueSetCommand" Assembly="Commands">
<DataConnection Name="OrderPositionConvertDataConnection">
<Property Name="UpdateRows">
<Parameters>
<Parameter Name="RowIndices">
<DataConnection SourceDataConnection="OrderPositionConvertDataConnection">
<Property Name="RowsIndicesOf">
<Parameters>
<Parameter Name="ColumnNames">
<Structure Type="List">
<Item>ID</Item>
</Structure>
</Parameter>
<Parameter Name="Values">
<Structure Type="List">
<Item>
<Array>
<Source>
<Command Name="MaterialListFormShowCommand" Parameter="MaterialArrayToEdit" />
</Source>
<Select>
<Items>
<Item Type="Index">0</Item><!-- ID -->
</Items>
</Select>
</Array>
</Item>
</Structure>
</Parameter>
</Parameters>
</Property>
</DataConnection>
</Parameter>
<Parameter Name="ColumnNames">
<Structure Type="List">
<Item>ID</Item>
<Item>MaterialId</Item>
<Item>MaterialTitle</Item>
<Item>MaterialCategoryTitle</Item>
<Item>UnitShortTitle</Item>
<Item>UnitPrice</Item>
<Item>UnitPriceString</Item>
<Item>Quantity</Item>
<Item>TotalPrice</Item>
<Item>QuantityString</Item>
<Item>Updated</Item>
<Item>Deleted</Item>
</Structure>
</Parameter>
<Parameter Name="Values">
<Array>
<Source>
<Command Name="MaterialListFormShowCommand" Parameter="MaterialArrayToEdit" />
</Source>
<Select>
<Items>
<Item Type="Index">0</Item><!-- ID -->
<Item Type="Index">1</Item><!-- MaterialId -->
<Item Type="Index">2</Item><!-- Title -->
<Item Type="Index">3</Item><!-- MaterialCategoryTitle -->
<Item Type="Index">4</Item><!-- UnitShortTitle -->
<Item Type="Index">5</Item><!-- UnitPrice -->
<Item Type="Format">{5} руб./{4}</Item><!-- UnitPriceString -->
<Item Type="Index">6</Item><!-- Quantity -->
<Item Type="Format">{7} руб.</Item><!--TotalPrice-->
<Item Type="Format">{6} {4}</Item><!--QuantityString-->
<Item Type="Value">True</Item>
<Item Type="Value">False</Item>
</Items>
</Select>
</Array>
</Parameter>
</Parameters>
</Property>
</DataConnection>
</Command>
С помощью get-проперти RowsIndicesOf у ConvertDataConnection находим индексы строк, в которых нужно обновить информацию о товарах.
Чтобы при удалении позиций различать позиции сохраненные в базе данных и недавно добавленные в заказ, создадим объект Variable вида:
<MyObject Name="MaterialArrayToDeleteVariable" Type="Variable" Assembly="SimpleControls">
<Value>
<Array>
<Source>
<DataConnection SourceDataConnection="OrderPositionConvertDataConnection">
<Fields>
<Field Name="ID" />
<Field Name="IsNew" />
</Fields>
</DataConnection>
</Source>
<Select>
<Items>
<Item Type="Number" />
<Item Type="Index">0</Item>
<Item Type="Index">1</Item>
</Items>
</Select>
<RightJoin>
<OuterArray>
<Command Name="MaterialListFormShowCommand" Parameter="MaterialArrayToDelete" />
</OuterArray>
<Keys>
<Inner Index="1" Type="IntegerDataType" />
<Outer Index="0" Type="IntegerDataType" />
</Keys>
<Result>
<Inner Index="0" /><!-- Number = RowIndex -->
<Inner Index="2" /><!-- IsNew -->
</Result>
</RightJoin>
</Array>
</Value>
</MyObject>
Объединяем два массива по временному идентификатору записей, и через селектор Number получаем индексы строк. Дальше в командах будем проверять значение во втором поле результирующей таблицы и по нему выделять позиции заказа сохраненные в базе данных.
Первом создадим команду для отмечания позиций на удаление из базы данных:
<Command Name="OrderPositionToDeleteFromDatabaseCommand" Type="ValueSetCommand" Assembly="Commands">
<DataConnection Name="OrderPositionConvertDataConnection">
<Property Name="UpdateRows">
<Parameters>
<Parameter Name="RowIndices">
<Array>
<Source>
<Object Name="MaterialArrayToDeleteVariable" />
</Source>
<Filter>
<Index Value="1" /><!-- IsNew -->
<Value>False</Value>
</Filter>
<Select>
<Items>
<Item Type="Index">0</Item><!-- RowIndex -->
</Items>
</Select>
</Array>
</Parameter>
<Parameter Name="ColumnNames">
<Structure Type="List">
<Item>TotalPrice</Item>
<Item>Quantity</Item>
<Item>Updated</Item>
<Item>Deleted</Item>
<Item>IsNew</Item>
</Structure>
</Parameter>
<Parameter Name="Values">
<Array>
<Source>
<Command Name="MaterialListFormShowCommand" Parameter="MaterialArrayToDelete" />
</Source>
<Select>
<Items>
<Item Type="Value">0</Item>
<Item Type="Value">0</Item>
<Item Type="Value">False</Item>
<Item Type="Value">True</Item>
<Item Type="Value">False</Item>
</Items>
</Select>
</Array>
</Parameter>
<Parameter Name="ReplicateValues">False</Parameter>
</Parameters>
</Property>
</DataConnection>
</Command>
В поле Deleted ставим значение True - тем самым помечаем для удаления. Дополнительно сбрасываем значения итоговой цены и количества товара в позиции.
Позиций заказа, которые еще не сохраняли в базе данных, удаляем из OrderPositionConvertDataConnection:
<Command Name="OrderPositionToDeleteValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<DataConnection Name="OrderPositionConvertDataConnection">
<Property Name="DeleteRowsByIndices">
<Parameters>
<Parameter Name="Value">
<Array>
<Source>
<Object Name="MaterialArrayToDeleteVariable" />
</Source>
<Filter>
<Index Value="1" /><!-- IsNew -->
<Value>True</Value>
</Filter>
<Select>
<Items>
<Item Type="Index">0</Item><!-- RowIndex -->
</Items>
</Select>
</Array>
</Parameter>
</Parameters>
</Property>
</DataConnection>
</Command>
Отлично! Добавим Новые команды в Execution:
<Execution>
<ConditionExpression>
<Condition Name="MaterialListFormUpdatedTrueEqualCondition" />
</ConditionExpression>
<Commands>
<Command Name="OrderPositionToAddCommand" />
<Command Name="OrderPositionToEditCommand" />
<Command Name="OrderPositionToDeleteFromDatabaseCommand" />
<Command Name="OrderPositionToDeleteValueSetCommand" />
</Commands>
</Execution>
Запустим приложение и проверим функциональность на добавление, изменение и удаление позиций заказа. Позже добавим логику сохранения изменений в базе данных, а пока реализуем логику удаления позиции заказа из списка через свайп-меню.
Удаление позиции заказа
Для удаления позиции заказа из списка будем использовать контекстное меню, вызываемое свайпом карточки влево. Направление свайпа и элементы меню настраиваются - поэтому при необходимости можете настроить вызов меню через свайп вправо, или даже реализовать два разных меню на каждое направление свайпа.
SwipeMenu
Свайп-меню настраивается в тэге <SwipeMenu>
, определенном в CollectionView. Добавим в описание объекта OrderPositionCollectionView следующий код:
<SwipeMenu>
<RightItems>
<SwipeItem>
<Text>Удалить</Text>
<ImageSource>Images\delete.png</ImageSource>
<BackColor>SoftRed</BackColor>
<Commands> </Commands>
</SwipeItem>
</RightItems>
</SwipeMenu>
Тэг <RightItems>
содержит список элементов меню <SwipeItem>
, которые будут отображаться справа от карточки при свайпе влево. Если необходимо отображать меню слева от карточки (при свайпе вправо), то используем тэг <LeftItems>
.
Элементы свайп-меню описываются тегом <SwipeItem>
, в котором указывается текст надписи, путь до изображения, цвет фона и последовательность команд.
Добавим в файл описание используемого цвета для кнопки удаления:
<Color Name="SoftRed" Red="220" Green="90" Blue="90" Alpha="255" />
Запустим приложение, чтобы проверить отображение свайп-меню:


Команды удаления
Первым делом создадим команду для подтверждения у пользователя намерения удалить выбранный товар:
<Command Name="OrderPositionDeleteMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
<Caption>Удаление</Caption>
<Text>Вы действительно хотите удалить выбранный товар?</Text>
<Buttons Type="OkCancel" />
</Command>
Команду OrderPositionDeleteMessageBoxCommand добавим в тэг <Commands>
для <SwipeMenu>
списка позиций заказа.
Создадим условие проверки, что выбранная запись была недавно добавлена в заказ и еще не сохранена в базе данных:
<Condition Name="SelectedOrderPositionIsNewEqualCondition" Type="EqualCondition" Assembly="Conditions">
<Items>
<Item>
<Object Name="OrderPositionCollectionView">
<Property Name="SelectedItemValueByFieldName">
<Parameters>
<Parameter Name="FieldName">IsNew</Parameter>
</Parameters>
</Property>
</Object>
</Item>
<Item>True</Item>
</Items>
</Condition>
Создайте самостоятельно команду на удаление новой позиции заказа, назвав ее, например SwipeDeleteValueSetCommand, и команду на удаление из базы данных, например SwipeDeleteFromDatabaseCommand. Индексы строк, редактируемых в командах, следует получать из OrderPositionConvertDataConnection по значению из поля ID выбранной позиции заказа.
Создадим Execution, который будет отслеживать значение параметра Ok команды OrderPositionDeleteMessageBoxCommand, и в зависимости от условия SelectedOrderPositionIsNewEqualCondition будет вызывать одну из двух созданных выше команд.
Запустим приложение и проверим удаление позиций заказа.
Отлично! На следующем уроке рассмотрим работу с JSON на форме, для удобного сохранения
Итоги
На уроке мы рассмотрели .
Ответы
В архиве присутствуют xml-файлы форм для мобильного приложения и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.
Архив не содержит xml-файлы форм десктопного приложения.
Last updated