Урок 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, в котором будем возвращать общее количество записей в результате запроса:
Создадим условие для проверки количества строк в ClientPrimaryGetDataConnection, используя для этого get-проперти Count:
Создадим переменную для хранения общего количества записей клиентов в базе данных:
Таким образом, если сервер не вернул никаких строк, то берем ноль, иначе будем брать значение из поля Count, которое соответствует количеству записей в базе данных.
Создадим условие проверки этой переменной, с помощью которого будем блокировать кнопки переключения по страницам:
Для объекта TotalItemsLabel скорректируем значение тэга <Text>, указав количество строк которые вернул сервер:
Переход между страницами
Создадим переменную для хранения номера текущей страницы:
Укажем эту переменную в тэге <Text> объекта CurrentPageTextBox.
Создадим переменную для расчета количества страниц:
Где условие ItemsPerPageComboBoxIsNullCondition имеет вид:
Укажем переменную TotalPagesVariable в тэге <Text> объекта TotalPagesLabel.
Команда изменения номера текущей страницы на произвольное значение:
Команда для перехода на предыдущую страницу:
Команда для перехода на следующую страницу:
Условие проверки, является ли текущая страница первой из доступных:
Условие проверки, является ли текущая страница последней из доступных:
Теперь можем скорректировать описание кнопок перехода по страницам:
Но переход между страницами возможен и при ручном вводе значения в поле CurrentPageTextBox. Для этого создадим условие для проверки события нажатия клавиши Enter при редактировании текстового поля:
Создадим условия проверки введенного значение на удовлетворение минимального и максимального количества страниц:
Создадим Execution, в котором будет отрабатывать событие нажатия клавиши Enter в текстовом поле CurrentPageTextBox:
Запрос на просмотр
В ClientPrimaryGetDataConnection добавим параметры Limit (количество записей на странице) и Page (номер текущей страницы).
Так же необходимо переделать фильтрацию архивных и актуальных записей. Для этого из колонки Archive в таблице ClientDatabaseTable удалим тэг <Filter>, и добавим в ClientPrimaryGetDataConnection одноименный параметр.
В запрос ClientSelectSqlQuery добавим переменные для новых параметров:
Запустите проект и проверьте загрузку формы и работу постраничного просмотра списка клиентов.
Самостоятельно
Реализуйте постраничный просмотр на главной форме.

Загрузка файлов
Дальше рассмотрим возможность загрузки файлов на сервер, скачивание и просмотр файлов через клиентское приложение.
В карточке клиента реализуем возможность добавлять фото, которое будем сохранять на сервер.
База данных
В таблицу template.client добавим колонку photo_file_id, которую через внешний ключ привяжем к таблице public.file:
В таблице public.file хранится вся необходимая информация о загруженном файле: имя файла без расширения (name), полный путь до файла на сервере (path), дата загрузки (date) и уникальный guid-идентификатор.
Подготовка формы
Начнем с того, что на форму карточки клиентов добавим графический элемент PictureBox, в котором будет отображаться фото клиента.
Перейдем в файл TemplateClientEdit.xml
Внутри панели ContentPanel создадим две панели: MainPanel, в которую перенесем все имеющиеся поля на форме, и PhotoPanel, в которой будут располагаться объекты для работы с изображением:

Скачайте архив с фото, которое будем использовать в качестве заглушки, и распакуйте его в папку \Template\Projects\1. Template\Forms\Images.
Добавим в PhotoPanel объект типа PictureBox:
В качестве значения тэга <Image> мы будем передавать ссылку с GUID файла, расположенного на сервере. По этой ссылке объект PictureBox самостоятельно скачает файл с сервера.
В тэгах <NullImage> и <ErrorImage> указан относительный путь до изображения, которое будет отображаться пользователю, если тэг <Image> имеет значение NULL, или изображение загружено с ошибкой.
Скорректируем запрос ClientByIdSelectSqlQuery, добавив в него поля PhotoGuid и PhotoGuidPath. Первое поле будет содержать чистый guid файла, который нам понадобится для скачивания изображения перед открытием на клиентской машине. Второе поле - guid-ссылка на файл, которую будем передавать в PhotoPictureBox.
Добавим новые поля в ClientPrimaryGetDataConnection и укажем PhotoGuidPath в качестве значения тэга <Image> PhotoPictureBox.
Добавим кнопки добавления и удаления изображения:
Запустите проект и проверьте расположение объектов на форме.

Редактирование изображения
Изображение будем выбирать с помощью диалогового окна выбора файлов, которое будем открывать по команде типа FileDialogShowCommand. Создадим такую команду:
Добавьте вызов этой команды на кнопку PhotoEditButton.
Обращаясь к параметру FullPath результата выполнения команды FileDialogShowCommand, мы получим полный путь до выбранного файла, который и передадим в наш объект PhotoPictureBox через set-проперти Image:
Создадим Execution для обработки результата выполнения команды выбора файла:
Удаление изображения
Создадим условия для проверки наличия пользовательского изображения в поле PhotoPictureBox. Для этого через get-проперти CurrentImageSource будем получать источник отображаемого изображения и сравнивать его со значениями, возвращаемыми get-проперти NullImage и ErrorImage.
Для удобства использования объединили обе проверки в одно условие PhotoPictureBoxIsNotEmptyCondition, которое укажем на кнопке PhotoDeleteButton в тэге <Enabled>.
Создадим команду для удаления пользовательского изображения из PhotoPictureBox.
Добавьте вызов этой команды на кнопку PhotoDeleteButton.
Сохранение изображения
Для отправки файла на сервер создадим команда типа UploadFileCommand:
Серверная часть сохранит файл в хранилище, имя которого указывается в тэге <Storage>. Так как у нас этот тэг отсутствует, то в качестве каталога для сохранения файлов будет использоваться хранилище с именем Default.
При успешном сохранении сервер сгенерирует уникальный guid-идентификатор, который вернется клиенту результатом команды UploadFileCommand.
Необходимая информация о загруженном файле будет сохранена в таблицу public.file.
Скорректируем команду SaveSequentialCommand, добавив вызов команды на передачу файла на сервер:
Добавим в ClientInsertSetDataConnection и ClientUpdateSetDataConnection параметр PhotoGuid, в котором будем передавать guid-идентификатор загруженного файла:
Скорректируем запросы:
Самостоятельно
В таком сохранении есть один большой недочет: при сохранении изменений в других полях (например, изменили дату рождения), если у клиента ранее была сохранена фотография, то команда PhotoPictureBoxUploadFileCommand будет выполняться повторно. Это приведет к очередному сохранению файла на сервер, а значит добавлению копии файла и перезаписи photo_file_id у записи в template.client. Придумайте и реализуйте механизм, блокирующий перезапись файла, если файл не изменялся.
Учтите один момент: сейчас в качестве параметра PhotoGuid передается результат команды PhotoPictureBoxUploadFileCommand, если она не будет вызываться, то параметре будет передаваться пустое значение, что приведет к удалению ссылки на фото у записи в template.client.
Просмотр изображения
Теперь нам необходимо реализовать просмотр изображения по двойному клику по объекту PhotoPictureBox. При этом будем различать две ситуации в зависимости от источника изображения:
из каталога на локальной машине;
по guid-ссылке файла на сервере.
Путь до файла на локальной машине
Когда мы добавляем новое изображение в PhotoPictureBox, get-проперти CurrentImageSource возвращает полный путь до файла на локальной машине. В таком случае мы можем открыть изображение с помощью команды типа ApplicationRunCommand, которая будет запускать подходящее приложение относительно переданного в тэг <Application> файла.
Создадим такую команду:
Условие PhotoPictureBoxIsNotEmptyCondition, указанное во вложенном тэге <Condition>, ограничит выполнение команды, если в объекте нет пользовательского изображения.
По guid-ссылке файла
Если в PhotoPictureBox передается guid-ссылка на файл, расположенный на сервере, то мы не можем воспользоваться командой ApplicationRunCommand - она не распознает эту ссылку. В этом случае нам нужно скачать файл, воспользовавшись командой типа DownloadFileCommand.
Создадим такую команду:
После скачивания файла с сервера команда сама откроет этот файл в подходящем приложении.
Реализация
Создадим условие типа StartsWithCondition, чтобы различать guid-ссылки на файла:
Создадим Execution для обработки двойного клика по PhotoPictureBox для просмотра изображения в стороннем приложении:
Итоги
В этом уроке мы рассмотрели паттерн Pagination, применение которого позволяет просматривать большой объем записей, разбив их на страницы. Познакомились с объектом PictureBox для отображения изображений на форме, а также рассмотрели команды UploadFileCommand и DownloadFileCommand для загрузки файлов на сервер и скачивания их на клиентскую машину соответственно.
Это был заключительный урок в базовом блоке. В следующих уроках уделим внимание режимам загрузки данных и многопользовательскому режиму.
Ответы
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.
Last updated