Урок 14. Постраничный просмотр
Бывают списки, в которых со временем может появиться очень много записей, и для удобства работы с ними лучше использовать постраничный просмотр.
Также в этом уроке мы рассмотрим работу с изображениями, передачу файлов на сервер и получение их на форму.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
Постраничный просмотр
Рассмотрим паттерн Pagination на примере формы списка клиентов.

Подготовка формы
Перейдем в файл списка клиентов (TemplateClientList.xml) и в ContentPanel добавим описание панели PagePanel с объектами, необходимыми для постраничного просмотра списка клиентов:
Скачайте архив с изображением для кнопок постраничного просмотра и распакуйте его в папку \Template\Projects\1. Template\Forms\Images\16x16.
Количество записей в ответе от сервера
Первым делом в запрос ClientSelectSqlQuery для списка клиентов добавим поле Count, в котором будем возвращать общее количество записей в результате запроса:
<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:
<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>
Создадим переменную для хранения общего количества записей клиентов в базе данных:
<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, которое соответствует количеству записей в базе данных.
Создадим условие проверки этой переменной, с помощью которого будем блокировать кнопки переключения по страницам:
<Condition Name="NumberOfClientsVariableEqual0Condition" Type="EqualCondition" Assembly="Conditions">
<Items>
<Item>
<Object Name="NumberOfClientsVariable" />
</Item>
<Item>0</Item>
</Items>
<DataType Type="IntegerDataType" />
</Condition>
Для объекта TotalItemsLabel скорректируем значение тэга <Text>
, указав количество строк которые вернул сервер:
Переход между страницами
Создадим переменную для хранения номера текущей страницы:
<MyObject Name="CurrentPageVariable" Type="Variable" Assembly="SimpleControls">
<Value>1</Value>
</MyObject>
Укажем эту переменную в тэге <Text>
объекта CurrentPageTextBox.
Создадим переменную для расчета количества страниц:
<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 имеет вид:
<Condition Name="ItemsPerPageComboBoxIsNullCondition" Type="IsNullCondition" Assembly="Conditions">
<Items>
<Item>
<Object Name="ItemsPerPageComboBox" />
</Item>
</Items>
</Condition>
Укажем переменную TotalPagesVariable в тэге <Text>
объекта TotalPagesLabel.
Команда изменения номера текущей страницы на произвольное значение:
<Command Name="CurrentPageVariableValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Object Name="CurrentPageVariable">
<Input />
</Object>
</Command>
Команда для перехода на предыдущую страницу:
<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>
Команда для перехода на следующую страницу:
<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>
Условие проверки, является ли текущая страница первой из доступных:
<Condition Name="CurrentPageVariableEqual1Condition" Type="EqualCondition" Assembly="Conditions">
<Items>
<Item>
<Object Name="CurrentPageVariable" />
</Item>
<Item>1</Item>
</Items>
<DataType Type="IntegerDataType" />
</Condition>
Условие проверки, является ли текущая страница последней из доступных:
<Condition Name="CurrentPageVariableEqualMaxCondition" Type="EqualCondition" Assembly="Conditions">
<Items>
<Item>
<Object Name="CurrentPageVariable" />
</Item>
<Item>
<Object Name="TotalPagesVariable" />
</Item>
</Items>
<DataType Type="IntegerDataType" />
</Condition>
Теперь можем скорректировать описание кнопок перехода по страницам:
Но переход между страницами возможен и при ручном вводе значения в поле CurrentPageTextBox. Для этого создадим условие для проверки события нажатия клавиши Enter при редактировании текстового поля:
<Condition Name="CurrentPageTextBoxKeyEnterPressCondition" Type="KeyDownCondition" Assembly="Conditions">
<Object Name="CurrentPageTextBox" />
<Key Value="Enter" />
</Condition>
Создадим условия проверки введенного значение на удовлетворение минимального и максимального количества страниц:
<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:
<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 одноименный параметр.
<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 добавим переменные для новых параметров:
<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:
<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.
<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.
Добавим кнопки добавления и удаления изображения:
<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. Создадим такую команду:
<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:
<Command Name="PhotoPictureBoxPhotoValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Object Name="PhotoPictureBox">
<Property Name="Image">
<Command Name="PhotoFileDialogShowCommand" Parameter="FullPath" />
</Property>
</Object>
</Command>
Создадим Execution для обработки результата выполнения команды выбора файла:
<Execution>
<ConditionExpression>
<Command Name="PhotoFileDialogShowCommand" Parameter="OK" />
</ConditionExpression>
<Commands>
<Command Name="PhotoPictureBoxPhotoValueSetCommand" />
<Command Name="FormChangedValueSetCommand">True</Command>
</Commands>
</Execution>
Удаление изображения
Создадим условия для проверки наличия пользовательского изображения в поле PhotoPictureBox. Для этого через get-проперти CurrentImageSource будем получать источник отображаемого изображения и сравнивать его со значениями, возвращаемыми get-проперти NullImage и ErrorImage.
<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.
<Command Name="PhotoPictureBoxNullValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Object Name="PhotoPictureBox">
<Property Name="Image">
<Object Name="PhotoPictureBox">
<Property Name="NullImage" />
</Object>
</Property>
</Object>
</Command>
Добавьте вызов этой команды на кнопку PhotoDeleteButton.
Сохранение изображения
Для отправки файла на сервер создадим команда типа UploadFileCommand:
<Command Name="PhotoPictureBoxUploadFileCommand" Type="UploadFileCommand" Assembly="Commands">
<FileName>
<Object Name="PhotoPictureBox">
<Property Name="CurrentImageSource" />
</Object>
</FileName>
</Command>
Серверная часть сохранит файл в хранилище, имя которого указывается в тэге <Storage>
. Так как у нас этот тэг отсутствует, то в качестве каталога для сохранения файлов будет использоваться хранилище с именем Default.
При успешном сохранении сервер сгенерирует уникальный guid-идентификатор, который вернется клиенту результатом команды UploadFileCommand.
Необходимая информация о загруженном файле будет сохранена в таблицу public.file.
Скорректируем команду SaveSequentialCommand, добавив вызов команды на передачу файла на сервер:
<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>
Скорректируем запросы:
Самостоятельно
В таком сохранении есть один большой недочет: при сохранении изменений в других полях (например, изменили дату рождения), если у клиента ранее была сохранена фотография, то команда PhotoPictureBoxUploadFileCommand будет выполняться повторно. Это приведет к очередному сохранению файла на сервер, а значит добавлению копии файла и перезаписи photo_file_id у записи в template.client. Придумайте и реализуйте механизм, блокирующий перезапись файла, если файл не изменялся.
Учтите один момент: сейчас в качестве параметра PhotoGuid передается результат команды PhotoPictureBoxUploadFileCommand, если она не будет вызываться, то параметре будет передаваться пустое значение, что приведет к удалению ссылки на фото у записи в template.client.
Просмотр изображения
Теперь нам необходимо реализовать просмотр изображения по двойному клику по объекту PhotoPictureBox. При этом будем различать две ситуации в зависимости от источника изображения:
из каталога на локальной машине;
по guid-ссылке файла на сервере.
Путь до файла на локальной машине
Когда мы добавляем новое изображение в PhotoPictureBox, get-проперти CurrentImageSource возвращает полный путь до файла на локальной машине. В таком случае мы можем открыть изображение с помощью команды типа ApplicationRunCommand, которая будет запускать подходящее приложение относительно переданного в тэг <Application>
файла.
Создадим такую команду:
<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.
Создадим такую команду:
<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-ссылки на файла:
<Condition Name="CurrentImageSourceIsGuidStartsWithCondition" Type="StartsWithCondition" Assembly="Conditions">
<Items>
<Item>
<Object Name="PhotoPictureBox">
<Property Name="CurrentImageSource" />
</Object>
</Item>
<Item>guid://</Item>
</Items>
</Condition>
Создадим Execution для обработки двойного клика по PhotoPictureBox для просмотра изображения в стороннем приложении:
<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